AI War 2:Adding Factions
EDIT: this Wiki page is probably outdated (again). so if you are on this page you can bring it up to date if you want. (or you know just move along if you don't, I don't care.) also some of the stuff will still be useful
This is going to a quick and dirty way of setting up a basic faction, as well as providing a heavily commented example faction that does work, but isn't balanced or otherwise useful for play. Overall, this should provide a good framework to start with that is easy for you to read and understand. You can also take additional code from other factions as necessary if you aren't sure what can or can't be added.
This is going to be building an Example Faction found on the forums
Contents
Getting Started
To start, go ahead and download the basic necessary components if you haven't already. This tutorial will be using Visual Studio 2017 (https://visualstudio.microsoft.com/downloads/), the release build of AI War 2 version 0.873, the AIWar2ExternalCode Project that is shipped with the game, and NotePad++ (https://notepad-plus-plus.org/download/v7.7.1.html). Note that you do not have to use the two tools listed if you don't want to download them, but they can be useful, at least for following along.
Get all of those set up and updated and all that fun stuff. Then, open your Project in Visual Studio. Go ahead and navigate to the Special Factions folder, right click on it, go to Add, then "New Item..."; you will then select Code File as the option you want to use. Naming it something that corresponds to the faction name is recommended, but not required. I named mine ExampleFaction.cs
INSERT IMAGE HERE
Starting the Code
Let's start by telling the code what namespaces we are using.
using Arcen.AIW2.Core; using System; using System.Collections.Generic; using System.Text; using Arcen.Universal;
We want to call Arcen.AIW2.Core and Arcen.Universal because they are baked into the game and contain additional things we can use and call. The system ones are just good defaults to have, and nothing else is needed.
Now go ahead and set up your namespace that we will be working in. For anything you do in External code, you will always want to use namespace Arcen.AIW2.External. Now, to define the class. Go ahead and use start with public class SpecialFaction_ExampleFaction : BaseSpecialFaction
. This says to make a new public class named SpecialFaction_ExampleFaction based off of the the BaseSpecialFaction. In other words, if it appears in the BaseSpecialFaction file, you can pull it over here and modify it easily.
At this point, we can start with the protected override values. There are only going to be three of them for this example, and they will be
protected override string TracingName => "ExampleFaction"; protected override bool EverNeedsToRunLongRangePlanning => true; protected override FInt MinimumSecondsBetweenLongRangePlannings => FInt.FromParts( 10, 000 );
While there are plenty more you can, for this basic example faction, those are the only three that are required. At this point your code should look like this, but minus the comments:
INSERT IMAGE NUMBER 2 HERE
Let's go ahead and start with making this faction friendly to the AI and its special forces, but hostile to everyone else. For simplicity, that will include factions that are allied with the AI, since this faction spawns Hunter/Killers, and they're mean. Think wasps, except worse. Anyways, to set it up, use public override void SetStartingFactionRelationships( Faction ExampleFaction )
. In that, go ahead and put base.SetStartingFactionRelationships( ExampleFaction );
just so it runs its normal stuff.
Here is going to be the most complicated part of the code. We're going to want to start a for loop that iterates through every faction so we can set their relationships up. for ( int i = 0; i < World_AIW2.Instance.Factions.Count; i++ )
will start that loop for us. In that loop, set up a new Faction variable that just pulls whatever faction we're on at this time so we can reference that later on a bit easier. Faction otherFaction = World_AIW2.Instance.Factions[i];
will do that for you and name the variable otherFaction. Now, check if otherFaction and ExampleFaction are the same thing. If so, just continue.
Now we want to start a switch loop. For our purposes, we're going to use it to switch between whether it is a Human faction, AI faction, or some other SpecialFaction. To start that, just use switch ( otherFaction.Type )
, then
case FactionType.Player: faction.MakeHostileTo( otherFaction ); otherFaction.MakeHostileTo( Examplefaction ); break;
What that does is if the FactionType we are looking at is a Player, it will make ExampleFaction hostile to it, and vice versa. You do need to make both hostile to each other, otherwise one faction will just stare at the other and ask "Why are you attacking me bro?" Go ahead and add similar code for FactionType.AI and FactionType.SpecialFaction. For the AI, we want it to be friendly, so use MakeFriendlyTo()
, instead of MakeHostileTo() like the Player code did.
For the SpecialFactions, we want to add some special lines in there to deal with the AI special forces, since we want them to be friendly to the H/K's as well. For this, we can do this:
case FactionType.SpecialFaction: if ( otherFaction.Implementation is SpecialFaction_HunterFleet || otherFaction.Implementation is SpecialFaction_AISpecialForces ) { faction.MakeFriendlyTo( otherFaction ); otherFaction.MakeFriendlyTo( ExampleFaction ); } else { faction.MakeHostileTo( otherFaction ); otherFaction.MakeHostileTo( ExampleFaction ); } break;
The If statement there will check if the SpecialFaction is the HunterFleet or AI Special Forces, and if so, make them friendly. The else handles anything that isn't one of those two, and sets them hostile to the H/K's.
After that the H/K's will have to be seeded (spawned) into the map. we have to override a void (start a new condition for the code to run and the type of code it is. should have told you that earlier. conditions can only run specific type of code in it. but the code for editing voids tends to be
public override void SeedStartingEntities_LaterEverythingElse( Faction faction, Galaxy galaxy, ArcenSimContext Context, MapTypeData mapType); { Code here }
This peace of code determines what and how entities are seeded into the AIW2 map:
Mapgen_Base.Mapgen_SeedSpecialEntities(Context, galaxy, faction, SpecialEntityType.None, "HunterKiller" , SeedingType.HardcodedCount, (Intensity*2), MapGenCountPerPlanet.One, MapGenSeedStyle.BigBad, 2, 0, PlanetSeedingZone.MostAnywhere);
The first 3 variables for this command can be left the same all the time. The next variable is the SpecialEntityType. This variable is not in the scope of this wiki page but becasue the H/Ks are not a special type this is set to none. the tag in this case is "HunterKiller" which is the appropriate Tag for this unit. A tag is a reference to an Entity/Entities. An entity can have multiple tags.
SeedingType determines some of the variables further on. for this wiki page it is a HardcodedCount. the next variable is determing how many H/Ks should be seeded on the map and the one after that determines how may H/Ks can spawn on an individual planet. The next variable is related to how this unit seeds in relation to other seeded units (edit if I'm wrong).
the next two number determine where the H/Ks spawn in relation to the Human and AI homeworld/s respectively. PlanetSeedingZone determines where on the planet the H/K spawns. i.e. on the edge or nearer to the center or both. It's been set to Most Anywhere becasue they will move anyways.
The Variable Intensity is created from the code
int Intensity = faction.Ex_MinorFactionCommon_GetPrimitives().Intensity;
and some XML code is required for this but I would say (READ THE COMMENTED XML FOR IT <add XML to the end>). Put the code above the code above like this:
public override void SeedStartingEntities_LaterEverythingElse( Faction faction, Galaxy galaxy, ArcenSimContext Context, MapTypeData mapType); { int Intensity = faction.Ex_MinorFactionCommon_GetPrimitives().Intensity; Mapgen_Base.Mapgen_SeedSpecialEntities(Context, galaxy, faction, SpecialEntityType.None, "HunterKiller" , SeedingType.HardcodedCount, (Intensity*2), MapGenCountPerPlanet.One, MapGenSeedStyle.BigBad, 2, 0, PlanetSeedingZone.MostAnywhere); }
The Main Code
Great... now that the H/Ks will spawn in what will they do? well currently they'll do nothing but that will change...
Now That the H/Ks spawn in the code required for them to move needs to be added in. (If this is not added in the H/Ks will sit in one spot and shoot hostile things near it). so to start we need to add another code type and condition. the Void override we'll be using is:
public override void DoLongRangePlanning_OnBackgroundNonSimThread_Subclass( Faction faction, ArcenLongTermIntermittentPlanningContext Context ) { GameCommand command = null; //Command saved for later }
This Void signifies that the code determines how the ships in your faction move. The next bit of code we are going to use is Delegate: The command is
faction.Entities.DoForEntities(delegate (GameEntity_Squad entity) { Code Here :) });
This code says that every entity within this code is to run through the option and execute any movement/ order/ attack command. there is only one type of unit in this faction but in the interest of demonstrating how this should be used with various Entity types.
If we do this pretending there are other units an If statement is required. the code for this is:
if ( entity.TypeData.GetHasTag( "HunterKiller" ) ) { Code Here :) }
This is just a Standard If Statement with it saying that if the selected unit currently has the tag "HunterKiller" Do stuff. the first thing to do after this is to set the entity Behavior (when not moving or stuff). The code required for this is:
entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, -1 );
This command tells the entity in question to turn to Bahaviour type Attacker Full. As a quick rule of thumb wheb coding within the delegate function. Anything with Entity.Something is directly affecting the chosen entity. The next two lines of code are variable that are required to get the H/Ks to move using commands. The code is:
Planet planet = entity.Planet; Planet neighbor = planet.GetRandomNeighbor( Context );
The first variable is the planet the Entity is on and the second planet is using the variable of the first planet and picking a random planet away from it. This is required for the code to actually make the entity move which is:
command = GameCommand.Create( BaseGameCommand.CommandsByCode[BaseGameCommand.Code.SetWormholePath], GameCommandSource.AnythingElse ); //Code above is used in determining what type of command it is command.RelatedEntityIDs.Add( entity.PrimaryKeyID ); //use the Entity ID (in this case the Entity currently selected) command.RelatedIntegers.Add( neighbor.PlanetIndex ); //Add the next planet onto the command types unique variable requirement (as other Gamecommand types //can be used Context.QueueCommandForSendingAtEndOfContext( command ); //Send Command out to the unit
DELEGATE must have all the parts of it 'return' to it. return is the command stated as return DelReturn.Continue for if you want the delegate to continue (which is what we want). this goes at the end of the delegate like this:
faction.Entities.DoForEntities(delegate (GameEntity_Squad entity) { return DelReturn.Continue; });
and after that your code should be complete. When the code is complete it should look like:
using Arcen.AIW2.Core; using System; using System.Collections.Generic; using System.Text; using Arcen.Universal; namespace Arcen.AIW2.External { public class SpecialFaction_ExampleFaction : BaseSpecialFaction { protected override string TracingName => "ExampleFaction"; protected override bool EverNeedsToRunLongRangePlanning => true; protected override FInt MinimumSecondsBetweenLongRangePlannings => FInt.FromParts( 10, 000 ); public override void SetStartingFactionRelationships( Faction faction ) { base.SetStartingFactionRelationships( faction ); for ( int i = 0; i < World_AIW2.Instance.Factions.Count; i++ ) { Faction otherFaction = World_AIW2.Instance.Factions[i]; if ( faction == otherFaction ) continue; switch ( otherFaction.Type ) { case FactionType.Player: faction.MakeHostileTo(otherFaction); otherFaction.MakeHostileTo(faction); break; case FactionType.AI: faction.MakeFriendlyTo( otherFaction ); otherFaction.MakeFriendlyTo( faction ); break; case FactionType.SpecialFaction: if ( otherFaction.Implementation is SpecialFaction_HunterFleet || otherFaction.Implementation is SpecialFaction_AISpecialForces ) { faction.MakeFriendlyTo( otherFaction ); otherFaction.MakeFriendlyTo( faction ); } else { faction.MakeHostileTo( otherFaction ); otherFaction.MakeHostileTo( faction ); } break; } } } public override void SeedStartingEntities_LaterEverythingElse( Faction faction, Galaxy galaxy, ArcenSimContext Context, MapTypeData mapType) { int Intensity = faction.Ex_MinorFactionCommon_GetPrimitives().Intensity; Mapgen_Base.Mapgen_SeedSpecialEntities(Context, galaxy, faction, SpecialEntityType.None, "HunterKiller" , SeedingType.HardcodedCount, (Intensity*2), MapGenCountPerPlanet.One, MapGenSeedStyle.BigBad, 2, 0, PlanetSeedingZone.MostAnywhere); } public override void DoLongRangePlanning_OnBackgroundNonSimThread_Subclass( Faction faction, ArcenLongTermIntermittentPlanningContext Context ) { GameCommand command = null; faction.Entities.DoForEntities(delegate (GameEntity_Squad entity) { if ( entity.TypeData.GetHasTag( "HunterKiller" ) ) { entity.Orders.SetBehavior( EntityBehaviorType.Attacker_Full, -1 ); Planet planet = entity.Planet; Planet neighbor = planet.GetRandomNeighbor( Context ); command = GameCommand.Create( BaseGameCommand.CommandsByCode[BaseGameCommand.Code.SetWormholePath], GameCommandSource.AnythingElse ); command.RelatedEntityIDs.Add( entity.PrimaryKeyID ); command.RelatedIntegers.Add( neighbor.PlanetIndex ); Context.QueueCommandForSendingAtEndOfContext( command ); } return DelReturn.Continue; }); } public override void DoPerSecondLogic_Stage3Main_OnMainThread( Faction faction, ArcenSimContext Context ) { } } }
XML will be done next. Please edit this page if you can make it better
The XML
now that the C# code is complete we need to do the XML. this XML is important because it points the game to the C# code. the complete XML will look like
<?xml version="1.0" encoding="utf-8"?> <root> <faction name="Ex faction" display_name="Example Faction" dll_name="AIWarExternalCode" type_name="Arcen.AIW2.External.SpecialFaction_ExampleFaction" team_center_color="c1560BD" team_border_color="c2E8B57" type="SpecialFaction" must_be_at_most_one="true" description="an amount of hunter killers (up to 20) wander around AI systems" can_be_targeted_by_annoyed_non_human_units="true" > <custom_field name="Intensity" display_name="Strength" description="Just how terrifying is this faction?" sort_group="200" setting_type="IntSlider" default="5" min="1" max="10"/> </faction> </root>
The XML version is consistant as with the encoding. DO NOT EDIT THIS unless you know what you are doing as usually the first line can be left in without consquence. <root> has to be there and within that <faction> must be there. within the tag faction there are various variables. in faction name this is the name of the faction's name (I don't use it for anything but there are uses for it I just havn't found it out). the display_name variable is the name of the faction to the player i.e. this faction in the faction meny is called Example Faction.
dll_name is where the Faction is 'kept' within the code. in my case it's stored in AIWarExternalCode.
typename is Important: if we go back to the class name (public class SpecialFaction_ExampleFaction) this is where the game looks for the code to run the fac. if you are modding AIW2 the Arcen.AIW2.External will always be consistant.
The two colours are in hexdecimal and determine the center and border color respectively. the type of thing this is is a special faction as we are adding a special faction into the game. read other documentation for more in depth analysis.
Must be atmost one is a true or false value which indicates wether you can have more than one instance of that faction. i.e. you can have two or more nanocaust faction yet you can only have one Astrotrain faction.this is set to true because this faction was not meant to be used in multiple instances.
the description variable is important to describe to users what you faction is doing.
can be targeted by annoyed non-human units indicates weather non player units can kill this faction.
NOTE other variables can be added into the faction but is not in the scope of this (quite hidious) wikipage. then you complete the tag for faction.
nested between the two tags is a lobby option relating to intensity for the faction. Custom Field Name is the variable name (look back into the C# to find the use in seeding). Display name is what the player sees i.e. the option will be called strength. description is the description givin to the player about the variable.
sort_group is a variable I don't know about so I just leave it as default. the setting type determines any following variables. i.e. it could theoretically be a dropbox like with the nanocaust. mid, max and default determine the minimum integer option on the slider, the maximum option and the default option respectively. then you need to close the tag with a / at the end. then you need to close your faction tag then your root tag.
Closing Thoughts & Additional Resources
Note: proofreading would be greatly appreciated and edits are always welcome. feel free to add more resources for modders below or change to more relevant resources if need be.
NAERLY COMPLETE R1 YAY :). if you do not understand what was talked about in this page you should ask around on sites below:
- (https://discord.gg/ewEnyJ7): AIW2 Discord
- (https://forums.arcengames.com/ai-war-ii-modding/): Arcen Forums modding section
- (https://forums.arcengames.com/ai-war-ii-modding/example-specialfaction-mod/): Special Faction mod