-
Procedurally Generated Maps Technical
A deeper dive into how the procedural map generator works
We have previously presented the new procedurally generated maps. Time to dive into the technical side! First off, lets have a look at the configuration that can govern the generation:
{ "Seed": 0, "MapLayout": "LeftRight", "MapStyle": "Normal", "Generator": [ { "Type": "PerlinNoise", "NoiseScale": 0.3, "BucketRatio": 1.0, "EntityRatio": { "Grassland": 1.0, "Forest": 0.3, "Mountain": 0.1, "Rocky": 0.1 } }, { "Type": "RandomPlacement", "LowerBound": 0.2, "UpperBound": 0.8, "EntityCounts": { "Barracks": 2, "Farm": 2, "House": 4, "Quarry": 2 } }, { "Type": "RandomPlacement", "LowerBound": 0.0, "UpperBound": 0.2, "TeamId": "Team 1", "EntityCounts": { "Barracks": 1, "Archer": 3, "House": 1, "Knight": 1 } }, { "Type": "RandomPlacement", "LowerBound": 0.8, "UpperBound": 1.0, "TeamId": "Team 2", "EntityCounts": { "Barracks": 1, "Archer": 3, "House": 1, "Knight": 1 } } ] }
The first few fields dictate the global style of the generation:
Seed – With random numbers, they can use a seed as a ‘starting point’. When defaulted to 0, all new maps will generate differently. If the seed is set then the generator will always come out with the same map.
MapLayout – Dictates the overall map layout. So far there are 4 possible values [ LeftRight, TopBottom, BottomLeftTopRight, Chaotic ]. These define the orientation of the battle where chaotic is both teams entities mixed across the map!
MapStyle – Dictates the overall map style, either Normal or Mirrored. The Mirrored version makes sure that the entities are identical on both teams sides.
Now we come to the real meat of the map generation, the Generator. This is a list of Layers, running from the top of the array to the bottom. Each layer will add (or subtract) entities to the current map state. Lets have a look at a sample of the resultant map:
Perlin Noise Layer
The first layer in that example is a PerlinNoise layer which generates the terrain (mountains, grassland etc). The BucketRatio dictates how much of the map is to be covered which is set to 1.0 so this layer covers the whole map. The EntityRatio details the used entities and fills up from top to bottom, possibly replacing any entities that is already in that layer. You may notice that the entity placement is not entirely random but seems to have zones – you can see that there is a large forest in the bottom left and a mountain range on the top for example. The random scheme that dictates this is known as Perlin Noise. This generates random numbers on a grid but there are smooth transitions of the numbers between each zone.
The parameter NoiseScale dictates the size of each region. The higher the value, the closer to pure random we get. Therefore, if we want a map with large regions of woodland for example then we could set a low noise for a layer with woodland.
These examples are generate using a single layer but of course one could add multiple layers with different entities and noise to get some very interesting features.
There is one additional consideration for using perlin noise. The noise generates a normal distribution centered around 0.5. Therefore, the code does a first pass and calculates the standard deviation of the noise with the given noise value, as the width of the normal curve varies with the noise.
With the standard deviation calculated, we can set Z where the area to the left of Z equals the entity ratio that the config dictates. This is the chance that the entity is present. When the perlin noise is in the area left of Z, we will have the entity at that position and likely to have it also in neighboring positions due to how the noise is generated. When the noise is greater than Z then we don’t have the entity at that position.
Random Placement Layer
The next few layers are RandomPlacement layers. These are much easier to understand as they effectively just run through the defined EntityCounts and places each entity within the given bounds. The UpperBound and LowerBound indicate the ratio across the map an imaginary line is drawn. Within these lines the entities of the layer can be placed. The MapLayout defines which orientation the lines are drawn.
Finally there is the Chaotic layout which throws away the boundary lines and puts the entities anywhere on the map!
The RandomPlacement layer is very well suited for entities which are more intractable such as team units and neutral buildings to fight over.
With the two layer types available at the moment some interesting maps can be made which offer a good game. However, there will be more layer types added as there is a lot of possibility of different features which will make the maps more rich and interesting to play on.
-
Procedurally Generated Maps
Introducing the new procedural map generator
Hand crafted maps are obviously pretty awesome and have had thought put into them. However, creating many maps takes time. Therefore let me introduce the procedurally generated map system.
The map above was generated with some loose specifications. Firstly, the terrain was generated taking options from plains, mountains, rocks and forests. These have been specified to take up the whole map with defined ratios. Then neutral buildings generated in the center of the map. Finally teams entities generated in the left and right.
Different map layouts and biases can be added. the layouts currently available are Left-Right, Top-Bottom, Bottom Left-Top Right and finally chaotic where the different teams entities are all mixed together!
Along with that there is an option to specify if the map is to be mirrored so that the layout of all entities are fair between the 2 teams.
The example here has a bias to give the blue teams more entities for an extra challenge!
Next Steps
The next development steps are to add layers for line based generation, for example addition of features such as roads and rivers. This will allow for some increased tactical considerations such as additional choke points and flanking.
Currently the maps are 1v1. The next iteration will include the addition of specifying more teams using the existing create map tool and allowing the generation to add more teams with spacing accordingly.
-
Transporting
The new transport feature of the game. Letting entities pick up and transport other entities.
The idea of an entity that can transport another entity is not new. For example, in a war game you could imagine an ‘APC’ style unit picking up and transporting an ‘Infantry’ type unit quicker than the ‘Infantry’ could move on their own. Obviously this opens up a large number of tactical options and adds a certain dynamic feel to a game, the idea that all of your teams entities are working together more seamlessly.
Therefore, we have not implemented transport to the game! In its current form, either the transporting or transported entity moves on top of the respective entity and can either ‘Load’ or ‘Pick Up’. Once a transported entity has another entity stored, it get a new option of ‘Unload’, which unsurprisingly drops the stored entity off in the transporting entities position. Of course, the transporting entity has presumably moved first and it could be many turns before unloading.
The current transport config options are as follows:
{ "CanBeCarried": true, # Default false "CarryingAllowedSelector": SelectorConfig, "AbilityConfig": SpecificAbilityConfig, "EntitiesDroppedTurnEnded": false, # Default false "CarryMovementOverride": 2 "EntityTagCarryMovementOverride": { "Infantry": 3 }, "EntityTypeCarryMovementOverride": { "Mech": 2 }, "DropOnDestroyed": true # Default false, "CanTransportEnemies": false # Default false "EntityCarryCount": 1, # Default 1 }
CanBeCarried, CarryingAllowedSelector – Tells the entities what they can transport and if they are allowed to be transported.
AbilityConfig – The standard config which details how many times the ability can be used per turn.
EntitiesDroppedTurnEnded – When the entity is dropped off, will it have its turn ended or the same state as if it was loaded this turn. i.e if its a new turn then the entity would be fresh with all its actions. If it had already used some actions and then got transported in the same turn, will it be able to use its remaining actions or have its turn ended.
CarryMovementOverride, EntityTagCarryMovementOverride, EntityTypeCarryMovementOverride – Defines if the transporting entity’s movement is effected by what its carrying. I like the idea that if its carrying something big then it would move slower. Also opens up the possibilities of moving quicker if things synergize or using the transport system for loading in boosts of other sort.
DropOnDestroyed – If the transporting entity is destroyed, defines if the transporting entities are also destroyed.
CanTransportEnemies – Experimental currently. Defines if the transport can pick up enemies. Maybe with a pop out mechanism so that the enemy entities can break free say at the end of the turn.
EntityCarryCount – Defines how many entities can be transported at once.
The configuration is currently fairly experimental and not set in stone. There are lots of possibilities for this kind of action and many subtleties. For example, there is currently no reason that recursive transport cant be done or different effects based on the synergy with the transporting and transported entities! I am certain that there will be updates to follow as this ability gets tested and morphs.
-
Feedback!
Adding a simple feedback and error reporting system
Just a quick one about the new feedback system in app. We have our browser based feedback page but when a user is actually playing the game, of course we want a way of letting them tell us how awesome it is.
Venturing into the technical details, the mechanism is simply to create a simple SMTP client which uses a dev email to send a message. An example which is written in C#:
MailMessage mail = new MailMessage(); mail.From = new MailAddress(myDevEmail); mail.To.Add(toEmail); mail.Subject = subject; mail.Body = message; SmtpClient smtpServer = new SmtpClient("smtp.gmail.com"); smtpServer.Port = 587; smtpServer.Credentials = new System.Net.NetworkCredential(myDevEmail, "some credential") as ICredentialsByHost; smtpServer.EnableSsl = true; ServicePointManager.ServerCertificateValidationCallback = delegate(object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { // Something return true; }; smtpServer.Send( mail );
This of course is powered by a simple UI to gather a users feedback and then it can be sent to
toEmail
. This basic method of sending emails to us for monitoring is also used for bug and error reporting.
-
Downloading config – Zips and Singles
How does the game client get the latest game files?
Realm Tactics runs all of its game system through downloaded config files. All terrain, units, attacks, sprites, AI config and map config are just files that need to be loaded for the game to work. The game client itself is completely divorced from all of that – not a single game mode included out of the box.
It may be tempted to shove all of it in a big zip file which can be downloaded easily, unzipped and give all that is needed to play the game; making sure its all perfectly intact and internally consistent. However, that is a very static way of doing it and clients would need to either download the whole lot again when an update is make or to become stale and miss latest updates.
At the other end of the scale, one could download each file individually when its needed. If this get performed each and every time then obviously the config would always be updated but that would be a load of downloads! We clearly need some compromise solution…
Meta Files
Our system is build on a bunch of meta files. These files as the name suggests contain the meta information about other files which should be present. For the most part, there is a meta file at each level of the config folder structure. In reality its fuzzier as meta files can reference other directories and also can reference sub-meta files (meta files in other locations), but for the most part consider that each directory (folder in the games file system) contains a meta file.
These meta files are extremely small comma separated data files that detail what other files should be present – one file reference per line. When the user navigates to the menu that is linked to a directory, the game makes a quick call and downloads the latest meta file. This is a very lightweight call but lets the game check what the state of the game should look like. Each meta file entry has a version number. The existing version number of an entry is compared with the new version number of the new downloaded meta file. If the version has updated, this indicates to the game that the file has changed and it can call and get just the updated files that have changed. As most files will remain unchanged session to session, most files wont have an updated version number so no action is needed. As you can imagine, this hits a good balance with keeping the files updated with not making too many calls for files. But as some of you may be thinking, that is still a lot of individual files that will need downloading when the game is first loaded of if there has been a major change, affecting a bunch of files. There needs to be a way of cutting down the number of downloads for large updates…
Downloading Blobs
As mentioned at the beginning of the article, having a big zip with all of the files is a great way of getting a large number of config files at once. So, we have a happy hybrid approach. The meta system describe above is still in play but an entry in a meta file can represent a zip file to download and extract. This will of course overwrite all files that are conflicts. What is clever though is that the zip file entry will act in exactly the same way as regular files, having a version number and only download if the latest meta file indicates that it has been updated. The zip file itself will also contain all of the files and the associated meta files which has a record of the latest version codes for its contained files.
So imagine a big update has been made. A new zip file is made which contains a bunch of files and meta files – all having their latest version codes. The game client downloads the zip and extracts it and puts in all the files that it needs. The game will no longer re-download the zip as its meta file already shows the latest version code. However, shock horror one of the files in the zip contains an error and an update needs to be maid! No worries, the zip file on the game config server doesn’t need updating, only the individual file that needs fixing and its individual meta version code bumped up. The game client will know that it has the correct zip, but sees the new version of the specific file and only that file is updated.
Win-win! We have a great amount of control to create zip files containing a bunch of config whilst also having the ability to make small tweaks here and there as needed. All the while the game clients only download what is needed and is always kept up to date!
-
AI dive in
How does the AI work under the hood? Lets have a dive in!
Turn based games like this live and die with the quality of their AI – Specifically the AI which guides the non-human controlled entities in the game. The code needs to know where to place an entity and what it will do to help it achieve victory. Games would not be as fun if all the enemy entities just moved around randomly! We need to make sure that a human player has a suitable challenge. For those interested, I thought I would take a delve into what it looks like under the hood.
Modules, Features and Weights
The basic structure is a bidding system between ‘modules’. These modules can represent a tactical choice that an entity may make such as ‘Attack’, ‘Purchase’ or ‘Capture’. The module details the steps that an entity will take to fulfill its particular tactical goal – for example for the ‘Capture’ module, it may have a move action to get into position, chained with a capture action to perform the actual capture.
All modules have a collective pool of features which it will use to determine what actions to plan and how effective that action would be. These features are shared across all modules but are weighted differently. For example, the ‘AttackEffectivenessFeature’, which gauges the value difference of performing an attack, the attack module would care a lot about; whereas the capture module does not really care about as it is not planning to attack. Another example such as the ‘PathedClosestProximityFeature’ which can determine for example how close an attacking enemy is, the attack module would care a lot but the capture module would also care about as it wants an interrupted capture. These will all have different weightings in each modules configuration.
- Module – A general tactical option that an entity can take e.g Attack, Capture
- Feature – A specific thing to consider when making a plan e.g Proximity of enemies
- Weight – How much each module cares about each features
Bidding
During each step of the AI turn, a round of bidding commences. Per entity, each module puts up its bid depending on how effective it things its particular action will be. We then take the winning bid over all modules to select the winning module, which itself contains the specific actions that it wants the entity to perform.
This bidding process actually includes multiple entities as entities can be used in any order. The highest bid over all entities therefore is what wins, and the winning entity will perform the actions as defined by its winning module. If an entity still has possible movements after it has done its actions, it will return to the bidding process and the bidding starts again. If an entity has ended its turn, it will be done and simply will not take part in any future bidding for the turn.
How are actions determined
We have discussed how the winning module and actions are determined, but what are these actions and how do they get created. This is slightly less ‘neat’ in that it is very determined by the specific tactic being considered.
An easy example to visualize is the Attack module. For the most part, this considers all possible positions that an entity can move and attack from, and considers the best attack to make against which enemy.
Considering a specific position, we can sum the calculate weighted result of each features:
A worked example of this could be the ‘PositionDefence’ feature. The attack module would care about this so the featureWeight would be large. If a position had defense which would help the entity on it then the featureResult would be increased. The total contribution for a high defense position for the attack module would therefore make a large contribution to the overall effectiveness.
Features come in all shapes and sizes and are not always as clear to fully calculate as a human – that is why delicate weighting is important. For example, the ‘AlliesCanProtectByAttackingAdjacentFeature’ considers if an attacking unit will be left on its own or if on the following turn if it gets attacked there will be allies around to counterattack.
There will be features which are positively weighted and also some which are negatively weighted (for example, the Capture module does not want any enemies near it to interrupt its capture so will favor capturing things outside the enemy’s range). All get calculated and we get our overall effectiveness for that position.
Some features will be very specific to the position i.e the defense is very position determined. However, some lead to other outcomes such as attacks possible after a movement, which enemies to attack when actually performing the attack action, enemy and own team entity compositions etc. There is an exhaustive list but as much as possible is considered to find the best effectiveness.
Now, the attack module has calculated the effectiveness at each position, it can select the position with the highest effectiveness and the corresponding attack with the best effectiveness at that position. With this information, the module can then go off an plan its actions which fit in to that position i.e a move and then attack.
This is the general process of a module but of course each is a bit different and most importantly the weighting of each feature will be specific to the module. Currently there are around 30 different features available and more are being added as the game develops. In principle, the more features to consider the better. There will be a diminishing return on the features weighting as they become more specific and situational but as long as its something that a human would also consider, its valid as a feature for the AI to also consider. This leads us to one of the limiting factors for this AI…. Performance!
Performance
In a perfect world, the AI would consider anything and everything to achieve its goal of winning (at least until it gets too good and we need to make it dumber for us humans to win!). So just keep adding more and more features right?
Yes and no. Consider the AI turn when it needs to take the turn of a large number, lets say 100 entities. Each of those entities has several (5 lets say) modules that it can consider. If each module has a large number of features, lets say 100 then for each bidding round, there will need to be around 100*100*5=50000 features to be calculated. That number is pretty arbitrary but just as an illustration, that’s a lot of feature calculations!
Each feature is by design moderately lightweight and focuses on a specific thing. This allows the weighting to be performed in a pure way and there are not ‘hidden features’ which could arise when a feature takes into account too much and some sub-features could be consumed by the overall design. However, even with this some complexity cannot be avoided. For example, calculating entity movement pathing over multiple turns (in principle as many turns as it takes to be able to reach any square of the map) is needed for many of the modules. While some caching/sharing of calculations between the features has been achieved, when an action such as an entity has moved then a good chunk of it will need re-calculating.
This is a live game such that each bidding process needs to take less than say 100 milliseconds or a user would notice a certain amount of sluggishness and have to wait for the AI turn to complete. There are animations which happen such as movement and attack animations but ultimately I want the AI to be snappy enough so that we could have a quick mode if the user wants to disable much of the animations. Indeed, a story for another post, but we have an AI training harness where battles are simulated thousands of times in parallel with the features tweaked to see if the AI can be improved also relies on a quick AI or training would take much longer.
Overall, you can see that performance considerations are crucial to the AI development process and each feature is evaluated to see if any corners can be cut or make the determination in the most efficient way possible.
Final Thoughts
The AI system is by far the part of RT that I am most proud of and one of the most fun and most frustrating thing to develop. Adding new features in isolation and testing them is a relatively simple process. Finding the correct weighting can be tricky but is incrementally getting better. Its the bizarre thing that its virtually impossible to trace back exactly why the AI did a thing that they did due to the total complexity the mass of features, modules and weights, but that it is making very sane choices that I, as a human, would also have done!
-
Conditions – How to win
Adding the ability to have conditions attached to a map/battle. Now there can be a winner!
Each map may have different conditions applied to it. The simplest version of this is when all enemy entities are destroyed then the remaining team wins. However, this game is all about being generic so the Conditions section has been added to the map configuration. Currently there is only one type of condition allowed; the WinCondition. There are various other condition types in plan:
Type Description WinCondition (Implemented) Defines what conditions are applied to each team to win the game MessageCondition (In Plan) Defines what messages are presented to the user under what conditions, time and position CampaignCondition (Ideas Board) Allows events and results in other maps to effect the current map Currently only the WinCondition has been implemented. A detailed example for a win condition is:
"WinConditions": [ { // A team can win by destroyin all enemy units "TeamIds": ["TEAM_1", "TEAM_2"] "EnemyEntitiesRemaining": 0, "EnemyEntitiesRemainingTagFilter": "Unit", "TurnNumber": 0 }, { // Team 2 will win if turn 20 is reached "TeamId": "TEAM_2", "TurnNumber": 20 }, { // Team 1 will win if they capture the enemy's 'Origin' entity type "TeamId": "TEAM_1", "EntityCaptured": { "Position": {"X": 9,"Y": 6}, "EntityType": "Origin" } }, { // Team 2 will win if they destroy the enemy's 'HQ' entity type "TeamId": "TEAM_2", "EntityDestroyed": { "Position": {"X": 3,"Y": 3}, "EntityType": "HQ" } } ]
Additional fields can be easily added to the WinCondition to allow different win types. With the framework, these different types of winning conditions can be added to any map. The EntityCaptured and EntityDestroyed fields creates an EntitySelector object which is created at the start of a map and gets attached to the specific entity. Therefore even if say the entity moves or otherwise the condition can still track it.
In the same way, additional condition types are planned to be created and will have a similar format. will allow a flexible and easily configurable way to add variety to a map.
The current work started is the MessageCondition. This will contain the conditions in a similar structure of the win conditions – and will also contain many of the same fields. This will be linked to a new Message object or a collection of Message objects which will have various fields such as [Text, Position, Team]. This is a basic structure and could get a lot more complex with intertwined conditions and messages. But as a start it will allow basic messages to be sent i.e a simple tutorial or story.
-
Animations!
An illustration of the new animation system which can be applied to any entity actions
An animation system has now been added which allows different spritesheets and configuration to be added to each entity actions. Here is a (very!) small sample of an attack animation and the associated config:
Which contains an idle animation and attack animation (cheating but actually using the same sprite sheet with the idle animation much slower!). The config which defines this unit is as follows:
{ "Name": "Peasant", "Description": "Standard, cheap but weak unit", "Sprite": { "FilePath": "units/peasant.png", "PixelsPerUnitX": 625, "PixelsPerUnitY": 675, "CycleTimeSeconds": 10, "Repeats": true }, "Tags": [ "Unit" ], "Movement": { "Movement": 2, "BlocksEnemies": true, "AbilityConfig": { "PointCost": 1, "UsesPerTurn": 1 } }, "Attack": { "DirectAttacks": [ { "Name": "Attack", "Detail": { "CanMoveAndAttack": true, "BasePower": 0.4, "Selector": { "AllowTags": [ "Unit" ] }, "TagPowerModifier": {}, "AbilityConfig": { "PointCost": 1 } } } ], "Sprite": { "FilePath": "units/peasant.png", "PixelsPerUnitX": 625, "PixelsPerUnitY": 675, "CycleTimeSeconds": 0.25, "NumberOfLoops": 4 } } }
There are 2 Sprite blocks, one on the Root of the config which gives the idle animation and one on the Attack configuration which is applied to any attacks that are made (unless a specific one is given to a sub attack config). The new sprite configuration is pretty clever, simply give it a sprite sheet (or a static image) with other optional values:
Key Description FilePath (Required) Points to the sprite sheet/image PixelsPerUnit(X/Y) Determines the unit size of each frame. The sprite sheet will automatically be split into each frame starting top left, proceeding along the row row and then down to the next. If not added then it assumes that the file is a single static image. CycleTimeSeconds How quickly a complete cycle showing all frames takes. Framerate Similar to the CycleTimeSeconds but allows the specific framerate (frames per second) to be defined. Repeats Define if its a continuous animation. For example, an idle animation repeats, a single attack does not. Animations that do not repeat complete for their entire duration and don’t get interrupted. NumberOfLoops How many loops of the animation to complete before it is finished. With the values give, it will automatically process the sprite sheet and incorporate it into the action at the particular level that is associated with. In this case the main attack confg but could be sub attacks, movement, capture etc.
More specific sub-mods are in the plan e.g movement in the cardinal directions (so having 4 for the set up, down, left and right which play as the entity moves) and any other specifics for other actions that I can think of. The system is generic such that the config is the same for each animation and all that is needed is adding to the format and adding the correct hook.
For illustration, the above units graphics are defined by this single example sprite sheet peasant.png:
-
Deep Dive into an entity
A deep dive into the structure of the entity config and what possibilities there are.
I have talked about what entities are and its associated config in general terms but actually deep diving into an example config may make things more apparent as to what all this looks like. First off is the tag – tags are concepts which essentially allow groups of similar entities share configuration. Remember, everything is an entity and share the same basic configuration structure. Tags allow reusable parts which apply to a bunch of entities. The main ones that are easy to imagine are for example player controlled units, terrain, buildings etc. As well as sub tags such as infantry or tanks which are set on a subset of units. Here is a basic ‘Unit’ tag configuration:
{ "Name": "Unit", "Description": "A controllable unit", "Defaults": { "Layer": 5, "Selectable": true, "Attack": { "CanBeAttacked": true }, "Defence": { "CanUseDefence": true }, "EntityAbility": { "AbilityPoints": 2 } } }
All values in the defaults are applied to any entity which it is associated with. Most of this is fairy self explanatory and keep it in mind until the end where we do a more in depth description in association with our ‘Archer’ unit:
{ "Name": "Archer", "Description": "Mobile range unit", "Sprite": { "FilePath": "units/archer.png", "PixelsPerUnit": 210 }, "Tags": [ "Unit" ], "Movement": { "Movement": 2, "AbilityConfig": { "PointCost": 1, "UsesPerTurn": 1 } }, "Attack": { "RangeAttacks": [ { "Name": "Fire", "MinRange": 2, "MaxRange": 3, "Detail": { "CanMoveAndAttack": true, "BasePower": 0.2, "Selector": { "AllowTags": [ "Unit" ] }, "AbilityConfig": { "PointCost": 1, "UsesPerTurn": 1, "EndsTurn": false } } } ] } }
Again this should be fairly self descriptive. On the root of the config we have:
Name and description Should be fairly obvious Sprite Defines the image used for the entity. There are plans to expand this to consume sprite sheets and allow animations to be added. Currently all units are static images. Tags A list of associated tags. They get applied from start to finish so any later tags will overtire. For example, an archer could have tags ‘Unit’,’Infantry’ where it uses the base unit tag but then infantry features get applied after and overwrite if any features match. After those fields, we have all of the Entity Abilities. Abilities are basically anything which dictates a particular feature of an entity. Abilities so far include:
Attack The bread and butter of a combat based game mode. Currently supports direct and range attacks with the option of a splash effect. In plan is a ‘chess style’ attack where you land on another entity. Capture Changes the team of an entity to your team. Currently works when you are on the target entity (e.g a building) and can take a configurable number of points to capture. Construct In development – This may just be superseded by Purchase. Defence Allows the entity to both supply and receive a defensive bonus. Heal In development – Restores life. May be superseded by Modifications. Modifications In development – Allows ‘status effects’ to be applied. These could include things that effect any of the other abilities. E.g ‘Injured’ which could effect movement range. Movement Defines both the self entity movement and also how other entities move through this entity. E.g a Wall entity could prevent enemies moving through it but allow your entities to move through it at a cost of 1, and not allowed to move itself. Purchase Allows additional entities to be purchased using the money/transaction system. Defines the cost to purchase this entity and also what entities this unit can purchase and under what conditions. For example a barracks can produce infantry. Money Defines the passive income that this entity generates per turn. This can also be used to add a cost of an entity per turn. Linked with the network concept Network Defines if the entity can be used as a node in a network. This allows the concept of connected zones which currently may share resources. Future plans are the network can be given features if specific entities are included or a certain size. E.g a hacking game where you have to connect things with cables that the enemy can sever. As you can see, there are a fair few abilities which can be applied to make each entity unique. I also plan to add to this list over time to add more abilities and add new features to existing ones (the list is endless!). And when used in different combinations can create some quite interesting ideas. Note: Any entity can use any ability! So get creative! I want to see lots of walking cities which produce flying scouts; or a fragile telepathic unit which can dominate the minds of its opponents and capture them!
Finally there are two more structures which are interesting which can be applied to each ability in various ways and allow quite a lot of specificity to each ability:
Selector Allows each ability to target a specific type of entity. Can define them explicitly by list of entity names or tags. For example, a dockyard can purchase entities with a selector of tag allow boats. A mountain can allow movement with a selector of tag allow infantry and planes but disallow tanks. AbilityConfig This defines when and how much an ability can be used. Each entity has a certain number of action points per turn and each action can cost a certain number of actions, can be used a certain number of times and if it ends the entities turn when used. I am in the process of writing some actual documentation as with a proper structure as there is a lot of different concepts here and can be used in a lot of different combinations. However as a taster I hope that you can see that there are a lot different combinations that are possible and this generic structure can allow a lot of different game modes.
-
Show off – Purchasing, Capturing and AI
A speedy demo of the Hacktics game mode showing off some of the latest features
As things progress, its hard to capture all changes in a single illustration. So here is a lightning example of two AI players battling over a network (hacktics game mode):
The Hacktics game mode is all about capturing ground which generates more income which allows more expensive units to be purchased. This mode uses the ‘network’ concept such that only continuously connected captured locations generate income. Therefore it is possible to cut through an opponents network and disrupt their income. As you can see, the AI occasionally purchases additional units from one of its construction buildings.
There are 3 units available:
- Workers which can capture network but not attack
- Viruses which have a direct attack
- DDOS which have a range attack
Each unit has a different cost and although not very visible in this video there are 3 different currencies at play. The more expensive units using the rarer currencies. In addition each location generates a different amount of income – the two ‘generator’ locations for example generate a high amount of the rarer resource.
It takes a worker 2 actions to capture a location and the worker has 2 actions per turn. This leads to the ‘ability’ config where each type of ability that any entity can do can have associated ability config – detailing how many action points each ability costs, if it can be used multiple times a turn, if it ends the entities turn etc.
The AI in this scenario acts reasonably intelligently (currently no training has been performed so not bad considering!). It understands the concept of the network and attempts to capture continuous locations in order to expand its income. It also attempts to purchase the most useful units it can given its current wealth. The units also attempt to engage intelligently and cause damage with an aim to get the best attack value while preserving themselves.
Note that each entity in this scenario have direct attacks although other forms of attacks are available which have been experimented with – in particular splash attacks which causes damage to multiple units at a time.