Basic XML modding
Contents
Brief Explanation Of XML For Non-Coders
XML (Extensible Markup Language) used to create ships, structures, tweak AI difficulty and more.
XML syntax requires all tags to be closed (unlike HTML)
all tags look something like this <tagName: stuff here if need be>
.
with every single tag you create or 'open' it must then be closed. tags can be closed in one of two ways. either like this: <tag1 />
or like this .<tag1> </tag1>
. this allows tags to be nested with tags like so:
<tag1> <tag2/> <tag1>
.
There is one tag that is required in XML modding at the start yet does not follow this rule and is one of two exceptions to this rule (the other being commenting). that tag is
<?xml version="1.0" encoding="utf-8"?>
that tag is used to tell the XML reader (parser) that this xml version is version 1.0 and the character set used (utf-8)
The most common mistake when XML modding will probably be forgetting to close a tag somewhere in your mod and there is very little to tell you where the offending tag is. (if you are XML modding and a big error message shows up it's probably due to forgetting to close a tag). (also please note there are some weird things with mod loader currently and partial records (overwriting) through XML mods so take care when using partial records).
Where To Put Your Mod (And An Example)
for creating a new starting fleet create an XML file. the name doesn't matter just make sure it's an XML file. and put it in: AI War 2\GameData\Configuration\FleetDesignTemplate or put it in a new XML mod folder and then FleetDesignTampate like: AI War 2\XMLMods\anyNameYouWant\FleetDesignTemplate
now in your new XML file for creating a new starting fleet you need to put in these tags in this fashion
<?xml version="1.0" encoding="utf-8"?> <root> </root>
These two tags will always be within any XML file used for general AIW2 modding. Within tags the XML parser can read variables used for various reason. for starting fleets we'll need another tag place within the root tag. this tag will contain variables within it. the tag will follow a pattern which will be:
<fleet_design name="InternalName" display_name="Name on starting menu" description="Description within selection menu" design_logic="InitialPlayerFleet" weight="100" include_full_cap_of_each_type="true" append_each_ship_description_to_main_description="true" don't touch the last four variables variables (please). btw weight is used more for the non- initial player fleets. (it determines it's likehood to be chosen) > </fleet_design>
within these two tags you will put another tag (or a few). the tag will be
<ship_membership name="Entityname" ship_cap_group="Centerpiece" weight="100" min="1" max="1"/> the 'variables' are the Name (please note this is an entity name and I will tell you how to obtain this name later). the weight can be left at 100 and the min and max should be the same. ship_cap_group should be left as is and is ONLY used for the fleet centerpiece (that needs a cap of one only).
the entity name can be found within AI War 2\GameData\Configuration\GameEntity (within the XML files). open any file you want (but preferably KDL_Ships_FleetShips) for finding entities to add to the squad. the entityname 9refered to as name within the XML will be found in
<entity name="VWing" The entity name. visuals="assets/_finalgamemeshes/fleetships/fighter/fighterold.prefab" icon_name="Official/Fighter" voice_group="Fighter" category="Ship" is_strikecraft="true" size_scale="0.9" visuals_scale_multiplier="6" collision_priority="100" display_name="V-Wing" description="Inexpensive and short ranged, adept at shielding allies and chasing down fleeing hostiles." starting_mark_level="Mark1" tech_upgrades_that_benefit_me="Generalist,Light" cost_for_ai_to_purchase="28" hull_points="2700" shield_points="1300" speed="AboveAverage1" metal_cost="1800" energy_consumption="400" armor_mm="50" albedo="0.3" engine_gx="8" mass_tx="0.21" ship_or_structure_explosion_sfx="ShipSmall_Explosion" ship_or_structure_explosion_if_on_other_planet_sfx="ShipLostOnOtherPlanet_Explosion" exp_to_grant_on_death="20" priority_as_ai_target="NormalFleetship" priority_as_frd_target="NormalFleetship" priority_to_protect="Expendable" > </entity>
Thus the your fleet will look like this:
<fleet_design name="ClassicStartingFleet" display_name="Classic Fleet" description="This classic mix isn't good at any one particular thing, but is well-rounded against any foe. The forcefields just make things even better." design_logic="InitialPlayerFleet" weight="100" include_full_cap_of_each_type="true" append_each_ship_description_to_main_description="true" > <ship_membership name="TransportFlagship_Starter" ship_cap_group="Centerpiece" weight="100" min="1" max="1"/> <ship_membership name="VWing" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="FusionBomber" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="ConcussionCorvette" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="ForcefieldFrigate" ship_cap_group="Frigate" weight="100" min="1" max="1"/> </fleet_design>
yes this is a base game fleet. (each fleet_membership tag represents a single unit's cap and type for the fleet). your final file should look like this:
<?xml version="1.0" encoding="utf-8"?> <root> <fleet_design name="ClassicStartingFleet" display_name="Classic Fleet" description="This classic mix isn't good at any one particular thing, but is well-rounded against any foe. The forcefields just make things even better." design_logic="InitialPlayerFleet" weight="100" include_full_cap_of_each_type="true" append_each_ship_description_to_main_description="true" > <ship_membership name="TransportFlagship_Starter" ship_cap_group="Centerpiece" weight="100" min="1" max="1"/> <ship_membership name="VWing" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="FusionBomber" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="ConcussionCorvette" ship_cap_group="Strike" weight="100" min="40" max="40"/> <ship_membership name="ForcefieldFrigate" ship_cap_group="Frigate" weight="100" min="1" max="1"/> </fleet_design> </root>
again please note this is a base game fleet.
Modding Existing Ships Or Similar
If you want to edit an existing ship, difficulty level, or whatever else, then you want to make a new xml file of your own inside your mod folder, matching the structure of the existing xml folder of whatever you want to mod. Note that your filename doesn't have to have any bearing on the original filename.
You now have two options on how to proceed:
Alter The Existing Item (is_partial_record)
Here's an example of how our second expansion actually "mods" the central ExternalConstants by having a file with these contents:
<?xml version="1.0" encoding="utf-8"?> <root is_partial_record="true" custom_int_zenithtrader_budgetpersecondforotherfactions="100" custom_FInt_zenithtrader_aibudgetmultiplierlow="0.5" custom_FInt_zenithtrader_aibudgetmultipliermedium="1.0" custom_FInt_zenithtrader_aibudgetmultiplierhigh="1.3" custom_int_zenithtrader_structurecost="270000" custom_bool_zenithtrader_spawnonplayerhomeplanet="false" > </root>
Note that basically is_partial_record="true" on any xml node (ship, settings, whatever) will let you start adding either new nodes (as in this expansion example), or overwriting prior entries (change the name or difficulty of a ship, make a ship faster, etc).
Copy The Existing Item And Make Changes (copy_from)
Here's an example from within the main base game itself:
<entity name="AstroTrainTankStyleHigh" cannot_be_stacked="true" copy_from="AstroTrainTankStyle" tags="AstroTrain,AstroTrainHigh,ShowsOnNormalDisplayMode" hull_points="3500000" shield_points="2500000" speed="BelowAverage2" > <system name="FusionBomb" display_name="Demolisher Fusion Bomb" category="Weapon" firing_timing="OnlyInRange" damage_per_shot="15000" range="Normal5" shot_speed="Slow" rate_of_fire="High" shots_per_salvo="20" fires_salvos_sequentially="false" shot_type_data="FusionBomb" base_percent_damage_bypasses_personal_shields="1" only_targets_static_units="true" > </system> </entity>
Essentially there are a lot of cases where we want to take some entity, in this case AstroTrainTankStyle, and we want to then make some changes to make a new variant (in this case called AstroTrainTankStyleHigh). In this case we added some guns, changed around hull points, shield, and speed, but otherwise left all the basic things from the underlying entity.
It's good practice to leave all of the fields in place except the ones you actually want to change, so that your variant will continue to evolve with the base unit if it gets some changes unrelated to the overrrides you have set up?
But hey, what about that "tags" field? That's being spelled out here, and identical to the original. That brings us to...
How Fields Act During copy_from Or is_partial_record
In general, single-value fields (like damage_per_shot) can either be required or optional.
For a system, damage_per_shot is actually required IF category="Weapon", but is an error if the category is anything else.
But even more than that, fields are very very rarely required if is_partial_record="true". In these cases, we pretty much assume that "if it was there before, and you didn't say anything about it, it will stay the same."
That is, generally speaking, also true for records where copy_from="something".
There's a list of exceptions below, most of them lists of items in one attribute, but what can you do if you don't like the way a field is behaving and want it to work differently?
Changing How Lists Are Read
- The game supports a few new general-purpose attribute variants:
- Essentially, if there is an attribute that is a list, and it is called bacon for whatever reason (this could be a mod or a main game attribute, after all), you can append these things after bacon to change how the xml is read in for that specific xml node.
- So if you have bacon="5,6,6", and normally that is read in as Uniqueness Required in the code, then you're powerless as a modder to get 5,6,6 as your results, instead only getting 5,6.
- But NOW you can add in bacon_uniqueness in order to override that to the value you want, as one example. This works with any attribute that is a list type, whether that is defined in the core game or in code you added for a mod.
- The valid new attribute variants are:
- [attribute]_uniqueness, with the values: Unenforced and Required. These overwrite whatever was passed into the FillList method in code.
- [attribute]_if_present, with the values: ReplaceExistingList and AddToExistingList. These overwrite whatever was passed into the FillList method in code.
- [attribute]_clear_before_reading, with the values: Never, Always, and IfNotPartialRecord. These overwrite whatever was passed into the FillList method in code.
exclude_children_from_copy
Normally when you do copy_from, it copies any child nodes that might be in there. Aka, if you are copying a GameEntity, it copies all of the system entries, all of the metal_flow entries, and a bunch of others. These are child nodes, not attributes.
If you want to have it not copy, for instance, metal_flow entries, then on your copy_from=whatever" entry you can add a exclude_children_from_copy="metal_flow". If you also want systems to be excluded, you can add exclude_children_from_copy="metal_flow,system".
Exceptions To The Rule
Normally lists are read in as: IfPresent.ReplaceExistingList, Uniqueness.Required, ClearBeforeReading.Never
Here's a list of exceptions, which we believe is comprehensive:
- exclude_children_from_copy
- IfPresent.ReplaceExistingList, Uniqueness.Required, ClearBeforeReading.Always