Example gameplay mod

Solver

Mohawk Games Designer
Joined
Mar 22, 2002
Messages
1,823
Location
Sweden
I'm happy to post an example of a gameplay mod that renames the Egyptian Medjay UU to Uber-Medjay and gives them 100 strength.

1738850260228.png


The mod also shows an example of basic localization use as the mod shows a different name if your game is set to German

1738850390364.png


To install the mod, it should be sufficient to extract the archive into the Mods subfolder of your Civ7 folder. The actual activation happens by setting things in the Mods.sqlite database but that seems to be automatic when the game detects a new valid mod.

Disclaimer: I am using the Linux build, which should behave the same as the Windows build but who knows.
(Per Gedemon, the Windows Mods folder is at "{user}\AppData\Local\Firaxis Games\Sid Meier's Civilization VII\")
1738850461082.png


Civ 7 uses the module feature for defining parts of gameplay. Each age is a module, the launch DLCs are modules, and mods are also modules. Correspondingly, the most important part of the mod is the .modinfo definition file

XML:
<?xml version="1.0" encoding="utf-8"?>
<Mod id="solver-example" version="1"
    xmlns="ModInfo">
    <Properties>
        <Name>LOC_MODULE_SOLVER_EXAMPLE</Name>
        <Description>This is a sample mod.</Description>
        <Authors>Solver</Authors>
        <ShowInBrowser>1</ShowInBrowser>
        <Package>SolverMods</Package>
    </Properties>
    <Dependencies>
        <Mod id="age-antiquity" title="LOC_MODULE_AGE_ANTIQUITY_NAME"/>
    </Dependencies>
    <ActionCriteria>
        <Criteria id="always">
            <AlwaysMet/>
        </Criteria>
        <Criteria id="antiquity-age-current">
            <AgeInUse>AGE_ANTIQUITY</AgeInUse>
        </Criteria>
    </ActionCriteria>
    <ActionGroups>
        <ActionGroup id="solver-mod-actions" scope="game" criteria="always">
            <Actions>
                <UpdateDatabase>
                    <Item>data/units.xml</Item>
                </UpdateDatabase>
            </Actions>
        </ActionGroup>
        <ActionGroup id="solver-mod-text" scope="game" criteria="antiquity-age-current">
            <Actions>
                <UpdateText>
                    <Item>text/en_us/UnitText.xml</Item>
                </UpdateText>
            </Actions>
        </ActionGroup>
    </ActionGroups>
    <LocalizedText>
        <File>text/en_us/SolverText.xml</File>
        <File>l10n/SolverText.xml</File>
    </LocalizedText>
</Mod>

Note that the mod id is set to solver-example at the start, which is important and should be unique.

The mod is going to update an Antiquity unit, but that's only possible when the game is actually in Antiquity. If you're in Age 2 or 3, Medjay aren't even present in the game data, so it'd be an error to update them. As such, the mod defines two criteria for its actions: antiquity-age-current to check if we're in Antiquity, and always to run always.

In the always case, we update the gameplay database with data/units.xml. If we're in Antiquity, we additionally update the text from text/en_us/UnitText.xml. [I think it would work fine to update the non-existent Medjay in other ages as well, but it's not a clean practice].

The strength update for the Medjay is simple:

Code:
<?xml version="1.0" encoding="utf-8"?>
<Database>
    <Unit_Stats>
        <Update>
            <Where UnitType="UNIT_MEDJAY"/>
            <Set Combat="100"/>
        </Update>
    </Unit_Stats>
</Database>

Equivalent to an SQL query, UPDATE Unit_Stats SET Combat=100 WHERE UnitType='UNIT_MEDJAY'

The Text update is, interestingly, not the same kind of update, rather you replace a whole row:

Code:
<?xml version="1.0" encoding="utf-8"?>
<Database>
    <EnglishText>
        <Replace Tag="LOC_UNIT_MEDJAY_NAME">
            <Text>Uber-Medjay</Text>
        </Replace>
    </EnglishText>
</Database>

There it is - a totally useless mod that can serve as a simple template for mods that alter existing gameplay properties.
 

Attachments

Last edited:
Thanks Solver !

Disclaimer: I am using the Linux build, which should behave the same as the Windows build but who knows.
View attachment 718275

Small addition for Windows users, the Mods folder is in the "{user}\AppData\Local\Firaxis Games\Sid Meier's Civilization VII\" folder (and not in "\Documents\my games\Sid Meier's Civilization VII")
 
Thanks, edited that path info into my post. Surprised that it's in the less obvious AppData location on Windows, while the Linux build is totally ignoring conventions (it should be under ~/.local/share by default, it's not).
 
I was surprised too, I suppose it may change.
 
May I ask how to get to know the UnitType name (like "UNIT_MEDJAY" for Medjay) of a unit?

If you open Civ7's install location, you can examine the files. Under Base/Modules there are three age modules and a couple common modules, so for most things you can guess where it'd be. The Medjay is e.g. an age 1 unit so it makes sense to look under the age-antiquity module, and most entities are defined in the data subfolder. So under age-antiquity/data you find a units.xml. It starts with a <Types> list that defines the names of units, it's similar for other XML files. For things like units and projects, the names are easy to figure out once you're in the file, though some other things like the narrative events have less pleasant names such as "3362C".
 
Thanks, I was almost afraid on launch I'll have to rewrite files, this tutorial is god-sent. Onto making my Ibn Battributa mod :goodjob:

Two questions

- are you sure you must replace the row? Replace keyword for localization was causing me issues in Civ 5, I had to learn to avoid it
- what is the package.json for? Is it needed?
 
(Per Gedemon, the Windows Mods folder is at "{user}\AppData\Local\Firaxis Games\Sid Meier's Civilization VII\")
FYI, for budding Windows power users, the Explorer shortcut for your user directory is %USERPROFILE%

The shortcut for AppData/Local is %LOCALAPPDATA%

1738934800056.png

1738934831153.png


(and even PowerShell respects ~ for the home directory, alas, cmd doesn't)
 
- are you sure you must replace the row? Replace keyword for localization was causing me issues in Civ 5, I had to learn to avoid it

I couldn't get it to recognize an Update for the text row, and couldn't find similar examples in the game files.

- what is the package.json for? Is it needed?

That's a Node.js package definition. It's not necessary for mods to work but maybe it would be needed if mods refer to other mods, not sure how that setup is. There's a lot of Web stuff in the game - the entire UI is basically a Web app - where I'm a little out of my comfort zone as I've never worked with that.
 
Think of package.json as your library folder for dependencies. It could list down other mods you require for the mod you're doing, or other libraries like say the i18n localization module if you're going to use it in any of your scripts.

Funnily enough I wonder if we can put a webapp inside Civ 7. Turn the Civiliopedia into a Vue app just because
 
Hi Solver!

I want to mod the disaster intensity, I found the file (random-events.xml), how do you suggest I go at it? I'm not very good with XML, for Civ 6 I used an sql file to mod the game around but I'm not very savvy there either, so I'm a bit lost here XD
 
As a bit of a test, I used your mod as a base to rename Friedrich and Prussia, its cities, and alter its colors. All fictional.

It was fairly straightforward, but for some reason the changes are not kicking in. The mod is detected according to Modding.log, it shows up in game on the mods list as Activated, but all mentions of Friedrich and Prussia are vanilla.

Any pointers? There were no errors that I could find. The logs don't seem to show anything amiss. If I were to guess, it'd seem like the modinfo actions are not triggering, for whatever reason.

EDIT: Fixed! The issue was in the scope of the action groups. Judging from official module game files, most of the changes I was making needed to be activated both within the "game" scope and the "shell" scope. The former can be inferred to be things that come into play in-game, and the latter stuff that could be visible as early as the main menu. So that was the case with my modified leader and mentions of the altered civ, the earliest activation needed to be in the shell environment.

XML:
<?xml version="1.0" encoding="utf-8"?>
<Mod id="eugen-wingarde" version="1"
    xmlns="ModInfo">
    <Properties>
        <Name>Eugen and Wingarde</Name>
        <Description>Contains a new leader, Eugen, and the Modern Age civilization, Wingarde.</Description>
        <Authors>Shadow</Authors>
        <ShowInBrowser>1</ShowInBrowser>
        <Package>ShadowMods</Package>
    </Properties>
    <Dependencies>
        <Mod id="base-standard" title="LOC_MODULE_BASE_STANDARD_NAME"/>
        <Mod id="age-modern" title="LOC_MODULE_AGE_MODERN_NAME"/>
    </Dependencies>
    <ActionCriteria>
        <Criteria id="always">
            <AlwaysMet/>
        </Criteria>
        <Criteria id="modern-age-current">
            <AgeInUse>AGE_MODERN</AgeInUse>
        </Criteria>
    </ActionCriteria>
    <ActionGroups>
        <ActionGroup id="eugen-wingarde-game" scope="game" criteria="always">
            <Actions>
                <UpdateColors>
                    <Item>data/colors/eugenplayercolors.xml</Item>
                    <Item>data/colors/eugenplayerstandardcolors.xml</Item>
                </UpdateColors>
                <UpdateText>
                    <Item>text/en_us/EugenLeaderText.xml</Item>
                    <Item>text/en_us/EugenLoadingText.xml</Item>
                    <Item>text/en_us/EugenUnlockText.xml</Item>
                    <Item>text/en_us/LeaderDialog_Eugen.xml</Item>
                    <Item>text/en_us/WingardeCivilizationText.xml</Item>
                    <Item>text/en_us/WingardeUniquesText.xml</Item>
                </UpdateText>
            </Actions>
        </ActionGroup>
        <ActionGroup id="eugen-wingarde-shell" scope="shell" criteria="always">
            <Actions>
                <UpdateColors>
                    <Item>data/colors/eugenplayercolors.xml</Item>
                    <Item>data/colors/eugenplayerstandardcolors.xml</Item>
                </UpdateColors>
                <UpdateText>
                    <Item>text/en_us/EugenLeaderText.xml</Item>
                    <Item>text/en_us/EugenLoadingText.xml</Item>
                    <Item>text/en_us/EugenUnlockText.xml</Item>
                    <Item>text/en_us/LeaderDialog_Eugen.xml</Item>
                    <Item>text/en_us/WingardeCivilizationText.xml</Item>
                    <Item>text/en_us/WingardeUniquesText.xml</Item>
                </UpdateText>
            </Actions>
        </ActionGroup>
        <ActionGroup id="eugen-wingarde-game-modern" scope="game" criteria="modern-age-current">
            <Actions>
                <UpdateText>
                    <Item>text/en_us/WingardeCityNamesText.xml</Item>
                    <Item>text/en_us/WingardeUniquesText.xml</Item>
                </UpdateText>
            </Actions>
        </ActionGroup>
    </ActionGroups>
    <LocalizedText></LocalizedText>
</Mod>

The new attached file has everything working, in case anyone wants a reference on how to implement similar changes.
 

Attachments

Last edited:
Nice!

So the "shell" is the game menus pretty much. The game only loads one age at a time. If you're playing Exploration, none of the Antiquity stuff is loaded. Where needed, entries are duplicated. In the menus, no age is loaded, so there are separate names for things then, and the menus are collectively the "shell" scope. If you try my initial Medjay example, you can note the difference as the unit would still be called Medjay when you select Egypt (shell scope) and would turn into Uber-Medjay once you've loaded into Antiquity.
 
So base-standard module is for things loaded in each age and shell scope for menu?
 
So base-standard module is for things loaded in each age and shell scope for menu?
Yes, although there's some age specific stuff still in the base-standard module with notes to move it out.
 
Nice!

So the "shell" is the game menus pretty much. The game only loads one age at a time. If you're playing Exploration, none of the Antiquity stuff is loaded. Where needed, entries are duplicated. In the menus, no age is loaded, so there are separate names for things then, and the menus are collectively the "shell" scope. If you try my initial Medjay example, you can note the difference as the unit would still be called Medjay when you select Egypt (shell scope) and would turn into Uber-Medjay once you've loaded into Antiquity.
The need (or not) to duplicate entries for the actions tends to be a bit confusing sometimes.

But I suppose it's an efficiency move, and I'd think overduplication matters much less in small XMLs than it would in much bigger assets (i.e. graphics).

So base-standard module is for things loaded in each age and shell scope for menu?
base-standard potentially corresponds to general in-game stuff. core and telemetry may be deeper and ancillary general functionality, respectively.

And then you have a separate module for each of the (so far) three ages, containing age-specific in-game functions, from units to age-specific Civs and the like. They probably reference age-specific audiovisual media assets as well.
 
Last edited:
What should I do if I want to change things that are more deeply nested and/or are outside of tags, like this from diplomacy-gameeffects.xml?
Code:
<Modifier id="PLAYER_DIPLOMACY_GRANT_POLICY_SLOT_COMPLETE" collection="COLLECTION_OWNER" effect="EFFECT_PLAYER_GRANT_TRADITION_SLOTS">
    <Argument name="Amount">1</Argument>
</Modifier>
 
Back
Top Bottom