Saturday, April 27, 2013

QuadTreeGood, SelectionBoxGood, HarvestingGood

Cultura now has harvesting of nodes in it. Next I'd like to work on these items:

  • QuadTree for unitsByLocation and structuresByLocation member variables inside Factions
  • Fixing... selection box problems again
  • Item graphics to be used for when a unit is carrying an item or when a structure holds items

The QuadTree will just be as basic as possible, I'm using it mostly for distance checks but also selection box. I may add non-QuadTree methods like findClosestSomething given a source location.

  • Vector of items
  • Four QuadTree children
  • Insert
  • Subdivide
  • QueryRange

The QuadTree will have a vector of items in it and also four children (which will be NULL at the start).

Insert will add the item to its children if it is in the range. If the capacity has been reached it then tries to add it to its children instead (creating them if necessary).

Subdivide creates four children that divides out the area of the current node into four equal parts

QueryRange will return the items in the node if the range specified falls within the quad. It then calls the same on all of its children and adds those items to its vector and then returns that. It should probably take in a vector* and create a new vector if it is NULL, which will eliminate the problem of having to create/destroy vectors in the recursive calls of QueryRange.

This function could also be used to find the closest "something" to a particular location. It could call QueryRange with that location as the range, so any items within nodes that hold that location would be returned. This gets me at least all the items in the same node as the location and also more that are around it. Then I simply do a distance check with all those items. Runtime should be n log n.

I'm contemplating only leaf nodes having objects within them. Additionally (which I have left out for now) I want kd tree for structures. Structures don't move around, thus there is no expensive tree balancing but they have excellent nearest neighbour algorithms (versus the mashing of my quadtree I will have to do to get the same level of performance). However, since for early stone age the only structures I expect are resource piles and caves, which are regularly abandoned when you need to move from land region to land region (to avoid exhausting resources), I'm not doing the kd tree until later. I'll call this "iterative development" instead of "I'm too lazy to do this shit right now".

I'd like to point out that the quad tree I am showing here is: very simple and splits the header file into two files. It's a templated class so everything has to be done inline but it doesn't mean your file arrangement can't still mimic a .h and .cpp split.

I am QuadTree:

#pragma once

#include <utility>

template <class T>
class CQuadTree
{
protected:
 std::vector<T> m_items;
 int m_capacity;

 CQuadTree<T>* m_topRight;
 CQuadTree<T>* m_topLeft;
 CQuadTree<T>* m_bottomRight;
 CQuadTree<T>* m_bottomLeft;

 std::pair<int,int> m_lowerLeftBound;
 std::pair<int,int> m_topRightBound; 

public:
 CQuadTree();
 CQuadTree(int capacity, std::pair<int,int> lowerLeftBound, std::pair<int,int> topRightBound);
 virtual ~CQuadTree(void);
 
 void insert(T item, std::pair<int,int> location);
 void subDivide();
 void queryRange(std::pair<int,int> lowerLeftBound, std::pair<int,int> topRightBound, std::vector<T>* itemsFound);
 void remove(T item, std::pair<int,int> location);  

 std::pair<int,int> getLowerLeftBound();
 std::pair<int,int> getTopRightBound();
};

#include "QuadTree.inl"

I am QuadTree inline file:

template <typename T>
CQuadTree<T>::CQuadTree()
{
 m_topRight = NULL;
 m_topLeft = NULL;
 m_bottomRight = NULL;
 m_bottomLeft = NULL;
}

template <typename T>
CQuadTree<T>::CQuadTree(int capacity, std::pair<int,int> lowerLeftBound, std::pair<int,int> topRightBound)
{
 m_capacity = capacity;
 m_lowerLeftBound = lowerLeftBound;
 m_topRightBound = topRightBound;
 m_topRight = NULL;
 m_topLeft = NULL;
 m_bottomRight = NULL;
 m_bottomLeft = NULL;
 m_items = std::vector<T>(); 
}

template <typename T>
CQuadTree<T>::~CQuadTree(void)
{
}

template <typename T>
void CQuadTree<T>::insert(T item, std::pair<int,int> location)
{
 if( !(location.first >= m_lowerLeftBound.first && location.second >= m_lowerLeftBound.second 
  && location.first <= m_topRightBound.first && location.second <= m_topRightBound.second))
 {
  return;
 } 

 if (m_items.size() < m_capacity)
 {
  m_items.push_back(item);
  return;
 }

 subDivide();
 m_topRight->insert(item, location);
 m_topLeft->insert(item, location);
 m_bottomRight->insert(item, location);
 m_bottomLeft->insert(item, location);
 return;
}

template <typename T>
void CQuadTree<T>::subDivide()
{
 if (NULL != m_topRight)
 {
  return;
 }

 int halfx = (m_lowerLeftBound.first + m_topRightBound.first) / 2;
 int halfy = (m_lowerLeftBound.second + m_topRightBound.second) / 2;

 m_topLeft = new CQuadTree<T>(m_capacity, std::pair<int,int>(m_lowerLeftBound.first, halfy), std::pair<int,int>(halfx, m_topRightBound.second));
 m_topRight = new CQuadTree<T>(m_capacity, std::pair<int,int>(halfx, halfy), m_topRightBound);
 m_bottomLeft = new CQuadTree<T>(m_capacity, std::pair<int,int>(halfx, m_lowerLeftBound.second), std::pair<int,int>(m_topRightBound.first, halfy));
 m_bottomRight = new CQuadTree<T>(m_capacity, m_lowerLeftBound, std::pair<int,int>(halfx, halfy));
 
 return;
}

template <typename T>
void CQuadTree<T>::queryRange(std::pair<int,int> lowerLeftBound, std::pair<int,int> topRightBound, std::vector<T>* itemsFound)
{
 //the query should start at the root and we need to have the same vector for all subsequent calls
 if (NULL == itemsFound)
 {
  itemsFound = new std::vector<T>();
 }

 //check if bounds intersect
 if(lowerLeftBound.first >= m_lowerLeftBound.first && lowerLeftBound.second >= m_lowerLeftBound.second 
  && lowerLeftBound.first <= m_topRightBound.first && lowerLeftBound.second <= m_topRightBound.second)
 {
  for (int i = 0; i < m_items.size(); i++)
  {
   itemsFound->push_back(m_items[i]);
  }
 } 
 else if(m_lowerLeftBound.first >= lowerLeftBound.first && m_lowerLeftBound.second >= lowerLeftBound.second 
  && m_lowerLeftBound.first <= topRightBound.first && m_lowerLeftBound.second <= topRightBound.second)
 {
  for (int i = 0; i < m_items.size(); i++)
  {
   itemsFound->push_back(m_items[i]);
  }
 }

 if (m_topRight)
 {
  m_topRight->queryRange(lowerLeftBound, topRightBound, itemsFound);
 }

 if (m_topLeft)
 {
  m_topLeft->queryRange(lowerLeftBound, topRightBound, itemsFound);
 }

 if (m_bottomRight)
 {
  m_bottomRight->queryRange(lowerLeftBound, topRightBound, itemsFound);
 }

 if (m_bottomLeft)
 {
  m_bottomLeft->queryRange(lowerLeftBound, topRightBound, itemsFound);
 }
}

template <typename T>
void CQuadTree<T>::remove(T item, std::pair<int,int> location)
{
 if( !(location.first >= m_lowerLeftBound.first && location.second >= m_lowerLeftBound.second 
  && location.first <= m_topRightBound.first && location.second <= m_topRightBound.second))
 {
  return;
 } 

 //this removes the item but does not perform delete
 int size = m_items.size();
 std::vector<T>::iterator iter = std::remove(m_items.begin(), m_items.end(), item);
 m_items.erase(iter, m_items.end());

 //only perform if we have not yet removed an item
 if( size == m_items.size())
 {
  if (m_topRight)
  {
   m_topRight->remove(item, location);
  }

  if (m_topLeft)
  {
   m_topLeft->remove(item, location);
  }

  if (m_bottomRight)
  {
   m_bottomRight->remove(item, location);
  }

  if (m_bottomLeft)
  {
   m_bottomLeft->remove(item, location);
  }
 } 
 return;
}

template <typename T>
std::pair<int,int> CQuadTree<T>::getLowerLeftBound()
{
 return m_lowerLeftBound;
}

template <typename T>
std::pair<int,int> CQuadTree<T>::getTopRightBound()
{
 return m_topRightBound;
}

For the selection box, I revisited it due to some problems I had. Namely, small square selections needed small fixes and large square selections weren't getting the right height/width. I do all the calculations inside the InputManager. Basically, the first problem was that I needed to calculate width and height using the original coordinate values. Previously I would pass the two game-coordinate corners to the selection algorithm but rather than fix that I instead put the calculation inside the InputManager which had the raw coordinates. Second problem was the offset that could occur depending on where the box started in a square. Third, the width distance is not the same as the height distance since it's across the square. Height is the same but due to the 45 degree rotation, you multiply it by cosine 45 and it comes out to the length of a square side (lucky!).

First we look at what the width and height values are:

Next let's look at what we really want. You'll notice that width is much larger than actual width because when the grid is rotated we want to catch the squares inbetween each column and thus the width is much larger. Height stays the same. (We could have made height much larger and width the same)

We get the width and height of the selection box. Then we adjust for where in a square you clicked. This may add upwards to 1 unit of distance to width or height.

Okay next we want to catch the squares which you just selected the tip of, which we do by ceiling the value of width and height.

Finally, we adjust width to add those inbetween columns. We simply add width - 1 to width.

For visual cues surrounding the carrying of items by units and structures that fill up with goods, I was thinking of adding another entity for the graphics. All of my units and structures derive from the same base class baseUnit. That has a virtual function called giveItem. Upon giving a unit some items and it accepts it, it should then determine whether or not the graphics of what it is holding should be changed. Likewise when it loses items (perhaps a takeItem function?) it should update graphics as needed.

Huzzah!

Now, here are some further issues I want to tackle.

I want to display an for where you click, for moving and other actions. There may be different colour x and whatnot to signify the different actions. Each x is created separately, shown and then hidden after some amount of time. The UIInputManager has a function that is called each frame rendered which has the timeSinceLastFrame in milliseconds. This would allow me to track time passed and hide the entity as needed. If I want to get real fancy, each x is tracked separately to allow you to create a lot of X's (much like how in Starcraft you would see many indicators of where you clicked).

I made units only capable of carrying a single item to make harvesting easier. I don't think that condition should remain. If they have capacity then they should take on stuff. I will have to change harvesting to match this difference. When they do a drop-off they must drop off each item they have until complete. It may mean that during drop-off the site is full or cannot take the other items and thus the unit must go to another drop-off site. The only difference in the code is that a unit must check if empty and if not empty, attempt to drop off the item at the front of the carried items list.

Next I had made items have two ids. An item id and a material id. An item id is like "vase", "pottery", "chair". A material id is like "oak", "pine", "lavastone", "limestone". A resource node would then contain an item and I had it such that item id was "oak" and material id was "oak". That's not really all that correct I think. Instead I will change it so that item ids are resource categories and material is then specifically "oak" or "pine".

As an example, a tree would have an item inside it. The item id is "wood", the material id is "darkwood". I'll make these ids as global const int and in a shared header. I want to make them as simple integers because then nothing needs to recompile if they don't need to look at specific constants. The only place I expect those constants to be directly accessed is in world generation code (that doesn't exist yet outside of test functions). As I add more ids, nothing will recompile (I hope). So a set of ids will be used for raw resources and then the rest for intermediate goods and finished goods. Thereby I don't need to add any more integers to an item to determine what it is and how it can be used.

Similarly, I wanted to add terrain ids. I could bucket these ids into ranges that represent how much they slow down a unit's movement. I'll likely add the slow-down later. However, impassibility could be something simple like if id < 10000 then passable terrain.

Most of the new graphics will be cheap-outs. That is, I'll draw iron node and then iron ore. Then I'll colour it green and bam! New resource called viperstone.

Resources nodes are grass, bushes and trees. They would be all over the place and it would be difficult not to click on them. So I was thinking maybe I need to change right click context to be a "move" command in more cases than it currently is. Right now, only if you click on an empty spot does the unit move there. Given the way the graphics look it's not readily apparent (especially lacking visual cues) that you just told a unit to harvest. And... I don't even think it's entirely expected. Instead I'd rather "hotkey" h to be harvest (I don't have a UI menu, so keyboard shortcuts are what is available for now). The only time it is harvest then, is if you pressed h (key up event) or if you told them to attack an animal.

Another thing to add a sense of industry was the need to process animals. Now, at first I would have liked hunters to kill an animal and then drag the whole animal back to your cave. But then problems with AI and carry limits abound. I can do that but I think it's just overkill. Most people are used to typical RTS style harvesting. If you kill an animal it becomes a pile of resources which you "collect" from. But I can add this in; you collect carcass and not straight up material.

For example, you kill a deer and then harvest it. You then collect "deer carcass" off of it. Following changes from above, the item id is deer carcass but the material id is stuff like "deer meat", "deer bone", "deer skin" and so on. You have to process the "deer carcass" and then it becomes "meat" / "deer meat", "bone" / "deer bone", "furs and skins" / "deer skin". And then you can use that to craft other items.

It was just a neat added level of "realism". I was reading about the Norse in Greenland (who eventually died out there) and how they would go on northern hunting trips to kill walruses (and also caribou). The walruses provided valuable ivory tusks but they needed to be processed (I wasn't clear on what processing the tusks meant other than it was a lot of work). Then once processed, they would sell it to arriving European trade ships for valuable goods such as timber, iron, stained glass or stone (okay, valuable only to the Greenland Norse because their land sucked so much).

This means that resource nodes could have one item (a single item id and a single material id associated with the item), but animals would have a list (a single item id which was "blah carcass" and then multiple material ids to represent the range of stuff an animal would provide). Also this makes a bit easier to determine what you get out of an animal. It's walking around (when alive) with that list of items already inside it, so some things might have antlers (bone), tusks (not sure what category I should put ivory in but bone sounds good), fur, skin and so on.

Generally I don't want to make an engineering-overkill factory class to generate me various animals. If I want to create a new deer and place it on the map, I'd have to know how much meat, bone, skin and so on that it contains. Where to keep that information? Well I'll probably create a class whose sole task is to generate various different stuff. I'm wanting to create 4 mundane and 4 fantasy stuff of each "thing", so that's really only 8 animals. Would I really want to create the infrastructure of a factory class just to make 8 different animals? Nah, an if-else or case switch is good enough (until it's not then I think about what I want to do). Similarly, this class should have methods to create animals, bushes, trees and so on. There's only eight of each type right now so I think it's okay. One way or another I have to write a lot of code for each one. This should be data-driven to be completely honest but uh... not doing that for now. No real excuse.

Thursday, April 25, 2013

World Generation

Okay this post, let's think ahead a bit. World generation!

I went around to various places that have world generation code and have determined the ultimate algorithm for Cultura. So ultimate, it might get changed in the future as I discover other ways of world generation.

We start with outlining the steps of my world generation. To the user, during the loading screen, it may say things like reticulating splines or recalcumalating solar flux but behind the scenes the actual rough steps would be something like:

  • Generate a random set of points based on a seed
  • Soften random nature of points through Lloyd's algorithm to get a more uniform distribution
  • Use Perlin noise to generate a height map for the different points
  • Select continent locations and then artificially bump up height in those areas
  • Mark everything below a particular height threshold as ocean
  • Mark everything above a particular height threshold as mountain. (Probably do it in the same loop as the step above)
  • Form rivers. Likely run them from the ocean coastline inland, moving along increasing height.
  • Rainfall and temperature gradient. Temperature increases as you move southward, the north is the arctic. Rainfall is more complicated but I may do a east to west calculation and have mountains "block" rainfall.
  • Biome selection per region.
  • For each type of resource (grass, bush, tree, animal, metal, stone etc) select a subset of points as super points. Each super region has a mutually exclusive selection of a specific version of the resource.
  • Figure out which region each tile is in
  • Spawn resources, animals into each region
  • Spawn starting factions into starting location regions

It's quite a list! And each one is a lot of work to implement. I won't be doing this until basically all the RTS features are good to go because it's both a heck of a lot to do and test (and a lot of small tweaking to get nice looking maps) but I really need everything else to be functioning to really try it out. That is, I need to be able to claim ownership of regions, abandon areas, build structures, harvest goods, store stuff, build units, fight enemies, hunt animals and so on. So this is just purely thinking ahead for now.

In order to generate a random set of points I'm not going to choose anything far more complicated than the c++ provided rand function. It allows me to use a seed which guarantees the same set of points. However, once I have a random set of points there is likely to be significant clustering. So I'd use Lloyd's algorithm to get a more uniform distribution which is more expected from a game perspective. Overly random leads to games with players are clear and significant disadvantages.

This creates a nice polygonal map for us to use as the basis on which we make all decisions in how the terrain looks in the end. We assign each point a height via some calculation using Perlin noise. But we want continents not a random spray of land. Why? Well my game is heavily dependent on the concept of diplomacy and trade and I think it heavily restricting should there be no oceans to create ocean-going trade (a mainstay of mercantile Europe for instance). Also I think ship-going trade is cool. So after Perlin noise gets us a random height assigned to each point, I wanted to select a subset of points (we could run our rand again and then pick existing points closest to those points). Those ones cause points around them to increase height by a set offset (say points below a certain distance and it probably shouldn't be cumulative, you can only apply the offset once). Then we apply the water/mountain threshold. We should be left with continents.

Next, we can easily assign a temperature to points. Based on it's northerly location, it gets colder. There's only a northern arctic in this game (which more or less reflects Eurasia versus the Americas). Then we assign rainfall. My idea for now is to have rain come in from the east and move westward, increasing with water underneath, dropping over land (increasing the rainfall of those squares), dropping more quickly as you move up in elevation, dropping more slowly as you move down elevation. But given that at this point, all you have is the list of points, it might be a tad hard to figure out how rainfall shifts as you move westward so it might be significantly simplified.

The temperature and rainfall determines biome (affecting the look of the region) and shifts the weighting of the random choice of plants, animals and resources that might occur in it. For each resource type we repeat the steps:

  • Generate a subset of random points and relax them to be more uniformly distributed.
  • Form super regions (points belong to the closest super point)
  • Select one type of the resource per region. No repeats. (This implies that the number of super regions is equal to the number of types possible)
  • Adjust resource information per point

This will create varying super regions of each resource, and then as you apply other categories there can be different overlaps allow areas which had zebras to have different vegetation and not the same combo throughout. We can also do less super regions and do the same calculation twice per resource type (such as animal) in order to have two different animals per point.

Now we get to the slow part. The points are meant to create a polygonal map via a Voronoi diagram. Then each tile needs to figure out to which Voronoi region it is in. Then we create region objects that understand what tiles it owns. For each region object it is associated with a single point and the point has the information regarding biome and resources. Thus now we are drawing out the terrain which will be saved in the TerrainManager object. Inside the TerrainManager object we have the super efficient storage of giant ass integer arrays.

Each tile gets set to a particular terrain. Then we run through each region object and populate it with a set number of grass nodes, bush nodes, tree nodes, resource nodes and animal herds. We may additionally set migration routes for each animal herd.

Bam! Now you have a procedurally generated world.

Okay so I've left the description on how to figure out which point (therefore which Voronoi region) a tile belongs to because it's a bit wordy. I'm concentrating on speed so I'm going to take a super dump on memory complexity. What we'll do is pathfind outward from each point to each tile, marking tiles based on the lowest distance to each point and truncating the pathfinding tree where we meet areas that are already marked and we are not closer. This process will self-terminate once all nodes are marked and best-path is found from each tile to each point.

for int i to number_of_points
    create pathFindTree
    add to array
end loop

bool hasUnmarkedTiles = true

while (hasUnmarkedTiles)
    for int i to number of pathFindTrees
     do one level of pathfind
    end loop
end loop

//pathfinding function
PathFindTree
{
    Node root
    Queue nodes
    int distance
    int pointIndex

    pathFindOneLevel
    {
     Queue newNodes
     node = nodes.popFront()
     while (node is not null)
     {
      //if the distance isn't better, we should terminate this branch
      if (distance < lowestDistance[x][y])
      {
       lowestDistance[x][y] = distance
       mark tile with pointIndex
       addChildren to queue
      }
     }

     distance++
     nodes = newNodes
    }
}

We'll just assume that I didn't make mistakes there. This gives us a linear time calculation for what each tile should be like. Then we can run through all tiles, set their terrain type and then also build the region objects. Then we run through each region object to populate resources and animals. Then finally we pick starting points for each faction and spawn their units.

Sunday, April 21, 2013

Harvest Node Action

For this update I will work on a few items:

  • Main Loop
  • Follow Action
  • Harvest Node Action

Main Loop

For the main loop there are two issues. The first is that time accumulated goes on even when the window loses focus. Due to this when the window is back to focus the game updates too quickly. We can solve this by not accumulating time when the window is out of focus. Second, the frame rate is currently not limited to 60 FPS and is not doing vsync.

There's two different changes that can be made to the code for time accumulation for updateTick calls. First we can check, via Ogre's API, whether the window is active and if not, choose not to update anything. Second we can cap the max accumulated time for updateTick. This makes sense if you think about it since no player can handle a massive spew of game updates. It's better for the game to stall than for the player to be out of control while things are happening

void CGameEngine::updateTick(unsigned long lTimeSinceLastCall)
{
 if (m_nWindow->isActive())
 {
  m_lTimeSinceLastUpdate += lTimeSinceLastCall;

  if ( m_lTimeSinceLastUpdate > (m_lTimePerUpdate * 5))
  {
   m_lTimeSinceLastUpdate = m_lTimePerUpdate * 5;
  }

  //TODO: probably set some sort of game speed setting
  if (m_lTimeSinceLastUpdate > m_lTimePerUpdate)
  {
   m_nAppStateManager->updateTick();
   m_lTimeSinceLastUpdate -= m_lTimePerUpdate;
  }
 }
}

I'm going to limit FPS to around 60 (or at least some number that makes it look okay on most LCD monitors which typically refresh at 60 hertz). So it seems that vsync doesn't happen in windowed mode and I'll like have the game run in windowed mode. Later if I create a fullscreen mode, then I will probably just let Ogre perform auto-rendering with vsync and not bother with manually calling renderOneFrame. However, I'm not convinced my code is correct. My problem is that you really need to wait on vsync and then render for it to be correct. In any case, for now, I'll just do 60 FPS.

void CGameEngine::render(unsigned long lTimeSinceLastCall)
{
 if (m_nWindow->isActive())
 {
  m_lTimeSinceLastRender += lTimeSinceLastCall;

  long timePerFrame = (1 / 60) * 1000;
  if (m_lTimeSinceLastRender > timePerFrame)
  {
   m_lTimeSinceLastRender -= timePerFrame;
   m_nRoot->renderOneFrame(timePerFrame);
  }
 }
}

It's probably important to note that since my game is running in windowed mode only, the active flag is not all that useful (it's always active if in windowed mode) and also vsync is ignored (at least on Linux for sure, and I think Windows as well). I'll still limit to 60 FPS so it will at least not nuke my gpu/cpu usage. My game will be built for Windows first and then Linux after (because that's easy). I'd leave Mac for the very last since the platform is... what I'll call "special needs" kind of platform :)

Follow Action

With move action already coded, most of what we need is already there for follow. We already detect if you clicked on another unit with your right-click and we already queue a follow action on the selected units' action stacks. What remains is coding the doAction of a FollowAction. In the updateTick of a unit the followAction->doAction is fairly simple: if squared distance is greater than 2, move closer. Otherwise do nothing. In fact this action is never finished. I could later add optimisation (if it proves to be an issue to constantly check distance because I think it is a tad overkill) that they only recheck distance if the unit moves (meaning that the target unit informs its followers that it is moving and only upon moving do the followers decide to also move).

I'm likely to leave that optimisation out until there is an event system. Units would instead check distance until they are close enough and then go into an idle state that waits on "move events" coming from the target unit. Then once that event is fired they go out of idle state and perform a distance check again.

The only optimisation that occurs right now is a polling frequency check if the unit is following another unit. Every ten cycles (about one second in real life) they will make a distance check. Otherwise they will wait. This means there will be a small delay before a unit moves to follow another unit.

Additionally, the move counter was originally part of the unit action but I've since moved it to the unit. Obviously, this makes the moveCounter persistent across actions and so you can't make a unit go faster with some strange method of enqueuing and dequeuing actions.

Harvest Node Action

Here's the more meaty task. I'm going to split the harvest action into two different actions. One is harvesting simple nodes (bushes, grass and trees) and the other is harvesting things you have to kill (currently just animals). In the future this might include looting corpses (for example, after a battle your army scours the field for goodies). Here I'm only doing Harvest Node.

For harvest node we now need structures (to store goods we have collected) and the concept of a carry limit for a unit (to determine when they are full and must return to a drop-off point). We'll ignore private ownership for now. In the stone age, you'll be living in communal society in which all goods are public (later iterations of the game will include more complex stone age societies that have private ownership).

As an infrastructure task, I've made units and structures derive from the same base class. Later when I add the combatStats class (which records hp, armour, resistances and damage types) I won't have to double my effort. Since the game development is going rather iteratively, I'm doing this refactoring after the fact rather than as part of a pre-planned design. Oh wells.

Here are the structures:

  • Cave
  • Resource Pile

A structure needs to know several pieces of information:

  • Combat Stats (leave for later
  • Food Capacity
  • Non-food Capacity
  • Food Stored
  • Non-food stored
  • Living Capacity (for later)
  • Furniture (for later)
  • In-building locations(like, where is the bed to sleep in?, leave for later)

As a side note, a cave structure implies three new graphics: a cave structure, a cave floor and a cave wall. We'll have all of these only take up 1x1 area. I'll have to think a bit about structures larger than 1x1 and how to figure out pathing and so on (like for instance, a person walks into a 2x3 bedroom and then has to figure out where the bed is inside it)

There's a small problem with adding terrain artifacts like cave walls:

Pooooop! The issue is that the tiles are rotated along the x-axis by 60 degrees. So everything is flatter in isometric view. I went to take a look at gnomoria and they seem to just draw it so that it fits into the flatter squares. So I'll just follow their lead.

As a side note, the cave walls are drawn like trees to get the same blocking happening.

Food capacity is important for calculating spoilage later. Non-food capacity is relevant for storage space inside vaults. How I might do graphics is another question for later. I'd prefer to be able to show a place is full and by what. Resource piles in particular are a zero-cost structure that also has storage space but does not protect against spoilage.

The harvest action is a state-machine action. A unit moves up to a node, harvests a node, makes a drop-off and then goes back to the first step. This repeats until it goes to the node and it is empty and the unit has nothing to drop-off (so there's a final trip where the unit returns to an empty node and then stands there doing nothing). I think this mimics expected RTS behaviour. I may want to additional have the unit continue to harvest if there is nearby resources of the same type, which mimics more advanced RTS behaviour.

While taking ownership of caves is not yet implemented, the testbed will simply give a cave to the player faction. When a unit cannot find a place to do a resource drop-off, the unit will create a resource pile at the closest storage location.

Here is a unit harvesting:

I'll make a more interesting screenshot next time when I add some visual cues. I figure some numbers fading in and out should be the right way to go. There's a few tidy-up debugging I need to do to make sure everything is absolutely okay. I'll leave that for later as I've blown much of the weekend at this point on this :P

Saturday, April 13, 2013

Move Action

For this update I'm doing purely the move action by itself and nothing else. I'll modify the architecture as I go along to implement each action.

A move command requires a few changes to the current design of Cultura. Some of these are common between all actions, some are specific to the move action.

  • Some way for units to know what action they are to do and to do them
  • Updating the position of a unit in all references
  • Figuring how to move

Okay so what does this mean? Well we're going to add an action stack to a unit. There will then be action objects. These will be subclassed for the different type of actions. I'll refactor after for any common members between different actions, but for now the only common component is a pointer to the unit to which the action is attached to (to make it easier to manipulate the unit from the action itself rather than passing states information around or firing events). Then updateTick will be modified to carry out the action. Then we'll need the different components to actually be able to carry out the action with pathfinding.

First let's look at updateTick:

We have an outer loop based on a set timer to call the highest level updateTick which then calls it on lower and lower level components until reaching actual actions which then carry out what needs to be done and modifies the relevant variables as needed (the Unit or GameState interface allows the action to do so).

For a move action we need a pathfinder object to figure out where the unit should move. This requires information about the world; which terrain is passable and where structures are. Then it needs a way to modify the position of a unit. It makes a "moveUnit" call on GameState and the GameState updates several variables (delegating the call to Faction which updates the Faction's hashMap of units by location and then calls move on the unit itself to modify the position). The units by location hash map on a Faction is used for determining which units are selected quickly when making a selection box.

Okay, so we've already done unit selection and now UIInputManager takes in a right-click, figures out if it is an empty square and then if so, issues a move command to a unit. The unit clears its action stack and then queues the move action. Now it'll move during updateTick based on its move speed.

Oh poop balls what happened? Actually the mesh used for the humans didn't do the alpha check when rendering. Let's add that now.

Huzzah! Units are moving!

So now to polish it up, there is some jittery movement which I think is related to the main game loop (might not be designed correctly and thus not the same amount of time passes between each updateTick) and I want an x to show up where you told the unit to move.

Saturday, April 6, 2013

Actions and Future Ideas

I'm at the point where I can start issuing commands to units. Part of the piping work also involved a few infrastructure changes to the code but most of it was changing things into pointers to make memory management more clear (more obvious when things can be deleted and where they exist in memory). That's more of a c++ specific issue what with my awesome well-thought-out tight coupling of systems requiring me to create and pass objects between components. Code quality is dropping fast but I'll try to keep a lid on technical debt with respect to bad code.

Anyway, the general design of how to give units orders needs to be planned out. On that note, I have a general design for this.

  • GameState->updateTick() calls updateTick on all factions
  • Faction->updateTick() calls updateTick on all units
  • Unit->updateTick() checks the top of the action stack and then performs that action, this may update their world position, may have to call react on a target and may have to broadcast location to other units nearby to check if they need to react to its presence

There are several types of actions:

  • Move
  • Harvest
  • Attack
  • Follow

And some actions are reactions:

  • Flee
  • AttackLimited

It might be possible to roll the reaction actions into the original set of reactions with a few member variables attached. For instance, flee might actually just be a move action. AttackLimited might just be an Attack action with information about how far a unit should pursue an enemy before giving up. Each action has certain conditions to be met before being completed and others are somewhat more complex. For instance, a harvest action will attempt to gather as much material as a unit can carry and then push a "return goods" action to the top of the stack.

Later when there are industries and other such activities, there will be more complex actions that can rest as a foundation for a unit. A blacksmith may have a "blacksmith" action and when it is reached it simply pushes several actions on top, such as a move action to get the unit to a workplace and then a build item action and so on. And then the blacksmith action is never completed, every time it is reached in the action stack, it simply pushes new actions onto the stack. This way a unit continues to be a blacksmith until it is cleared.

These actions and reactions will require a little bit of infrastructure. A unit will now have an action stack where the actions are pushed onto and popped when completed. Some actions are replaceable, it remains to be seen how complicated this may become due to the nature of the game (a Settlers style industry implies a lot of automated action scripts that may occur). For reactions, the biggest problem is reacting to the presence of an enemy. Either you flee or you attack but in both cases it requires a way to perform a distance check between all units at any given time where this condition can be met.

There are several optimisations I have thought about for this problem. The first is to have a world hash map that contains all the units bucketed by "super squares". That is, I reduced the entire map to a smaller number of squares and only perform distance checks within groupings of squares. It's sort of like an oct tree but not quite. Simpler for the purpose I have in mind. The other is to only perform a distance check between a unit that has moved (in its updateTick function) and other units "near" it as determined by those super squares. And finally, only certain units even care. That is, the world hash map only contains military units. All other unit types are simply oblivious to approaching enemies until they are attacked.

For pathfinding, I'd prefer to use A* pathfinding but beyond a certain distance threshold it may be best to use a blind greedy algorithm. Just move in a square that goes toward the target location. In later iterations of Cultura where I want infrastructure (roads are especially important), I may have to do something more fancy. As a rule of thumb in those cases, roads/waterways are preferred over self-made paths not only because they may be faster but they may also be safer. An easier way to handle it is to make the roads speed up movement enough that they are almost always preferred without any special consideration on top of that and also to use a limited A* search (only do a partial pathfind and then move on the partial path).

There's been a bit of code clean up as well. Most of it is switching things into pointers to make it more obvious when memory is/should be cleared. Later on when the first iteration of the game (the alpha version) is done I will probably then proceed to implement an event based system (or I may even wait until I make a first release of the game before making large infrastructure code improvements). The event system is the biggest improvement that could happen. I picture it to be perhaps a singleton accessible from anywhere to allow all the different components to fire an event and register to listen to events.

Finally, I've had some thoughts on what I'd want the alpha to be. Essentially I'm going to split the game into "stages". Each stage is reached by reaching the population count necessary for it. For every stage there are new problems that can happen. Those problems are all centered around three variables in your society: health, happiness and administration. Stage 0 is where you start and you have no considerations. You simply go around collect resources, craft items and grow as you see fit. Stage 1 introduces health, where you have to maintain health or people can die from disease. And then finally Stage 2 is where happiness is introduced and you could face emigration should happiness fall too low. There are more possible consequences for failing to maintain health/happiness at later stages.

The Alpha version of the game will just be Stage 0. I'll complete all the necessary components (structures, population growth, UI, actions etc) and all the polish work (loading screens, responsive UI, good user experience) and then I'll move onto working on Stage 1 and Stage 2 where I have a "complete" game and I'm simply adding features. The release candidates would be with the first three stages (0, 1, 2). Then I'd work on 3,4,5 that focus on a wider range of consequences. For instance, low happiness could result in a small revolt or low health could result in a plague outbreak whereas in the earlier stages only one person might die from illness or a small group of people would just leave your society peacefully.

The polish work will likely require the help of some friends to point out UI experience issues that I may overlook because I focus too much on implementing various features. It's important for the user to always be aware of what's important in the game, to waste as little time on trying to find buttons or information and for everything to be "obvious" but to maintain the game complexity. It should be easy to understand the complexity and be fun to play through it.

What is my current wishlist? For an alpha I'd like to have the following:

  • Actions - user input and reactionary ones
  • Resource piles - placing resources anywhere on the ground
  • Natural Structures - caves mostly and also a resource pile with a limit inside, eventually would like to have dwarven mountains, elven tree tops, goblin bogs
  • Fighting - different damage types, weapons, hp, armour, dying, dropping of equipment onto the ground, looting equipment
  • Population Growth - some way of having population growth
  • Loading Screen - loading screen for starting a new game or loading a game
  • New Game set up - a screen for setting up a new game
  • Loading and saving - serialising game objects and deserialising them for saving/loading
  • Map Generation - based on some settings be able to generate a new random map that is good to play on (fair, balanced, varied landscape, sensible positioning of animals and plants)
  • Update Tick - this is a more general concept, just making sure that game speed is respected, units are redrawn in the correct spot, UI elements such as selection circles appear and disappear properly
  • Large Maps - get as large of a map as possible, possibly use a texture atlas and a shader that renders a texture, memory management of all the game entities see how far the limit can go
  • Diplomacy - ability to speak with other groups, maintaining the faction list, ability to make treaties based on diplomatic technologies known
  • AI Opponents - ability to have AI manage units, collect resources, craft items, and make diplomatic approaches with other entities, have personality, act based on relations and personality
  • Wildlife - have wildlife flee upon being attacked, migrate aimlessly, seasonal migration, just look vibrant
  • Vegetation Growth - be able to have regrowth of natural resources such as trees and grass, be able to record planted/dug up vegetation so that regrowth reflects environmental changes made by player
  • Land Regions - have arbitrarily defined land regions for game purposes
  • Maturation - have plants, animals and people have a baby to adult process, possibly a death by age process as well

These are some post-alpha ideas:

  • Serialisation versioning - able to have older save games be translated into newer save games without null point exceptions and so on
  • Labour skills - people can get better at particular tasks and produce higher quality goods or product them faster
  • Injuries - damage sustained that requires medical technologies to heal
  • Seasons - resources in random piles spoil but not in structures, health concerns if there is lack of warmth, regrowth of vegetation and wildlife in spring
  • Families - death by age and children, as well as family-based resource management
  • Artificial structures - structures the players builds, ability for people to understand how to use them and/or have assignments or possession of it
  • Trade - permanent trade routes, festivals and trade festivals
  • Health/Happiness - maintaining a calculation of health/happiness, have consequences for having low health/happiness
  • Families - death by age and children, as well as family-based resource management