Creating gamemodes#

Creating a gamemode is significantly more complex than making mutators. The main differences are the number of things you must define in order to create a functioning gamemode.

For example, the client localisation, the way the gamemode is defined (FFA, TDM, etc), the scoring system, respawn system (FFA or TDM spawnpoints) and team mechanics must all be considered.

The mod.json#

The mod.json is responsible for governing when, and where your mod is loaded, and follows a layout that is fairly complicated at first glance. However, once you get the hang of it, it should be fairly easy to use.

{
    "Name" : "SimpleRandomiser",
    "Description" : "A randomiser gamemode that randomizes your loadouts!",
    "Version": "0.1.0",
    "LoadPriority": 1,

The script above defines the pubic and listed details of the mod.

"Scripts": [
    {
        "Path": "gamemodes/_gamemode_simplerandomiser.nut",
        "RunOn": "SERVER && MP"
    },
    {
        "Path": "gamemodes/cl_gamemode_simplerandomiser.nut",
        "RunOn": "CLIENT && MP"
    },
    {
        "Path": "sh_gamemode_simplerandomiser.nut",
        "RunOn": "MP",
        "ClientCallback": {
            "Before": "simplerandomiser_init"
        },
        "ServerCallback": {
            "Before": "simplerandomiser_init"
        }
    }
],

The script above defines both what functions to run, when to run them and WHERE to run them,

The first one being _gamemode_simplerandomiser.nut runs the server scripts, which handles the portion of everything related to the player, such as taking their weapons and replacing it with a different one.

Second one being cl_gamemode_simplerandomiser.nut is where the client scripts run to perform stuff locally on the player’s game, such as playing music, receiving announcement texts from the server and so on.

Lastly, sh_gamemode_simplerandomiser.nut is a shared script between server and client, in this case it runs your simplerandomiser_init in order to assign many variables for the server and client to “know” about this gamemode.

For example, both server and client needs to know whether if this gamemode exists in the private match settings, the scoring HUD and system, the spawnpoints configuration and many more.

    "Localisation": [
        "resource/simplerandomiser_localisation_%language%.txt"
    ]
}

This defines the path to the language file, and its main use is to localize strings such as the announcement texts, gamemode and so on.

Name this file mod.json, and it should go in the mods root folder, that being /yourmodname.

Here’s what the end result would look like:

{
    "Name" : "SimpleRandomiser",
    "Description" : "SimpleRandomiser",
    "Version": "0.1.0",
    "LoadPriority": 1,
    "Scripts": [
    {
        "Path": "gamemodes/_gamemode_simplerandomiser.nut",
        "RunOn": "SERVER && MP"
    },
    {
        "Path": "gamemodes/cl_gamemode_simplerandomiser.nut",
        "RunOn": "CLIENT && MP"
    },
    {
        "Path": "sh_gamemode_simplerandomiser.nut",
        "RunOn": "MP",
        "ClientCallback": {
            "Before": "simplerandomiser_init"
        },
        "ServerCallback": {
            "Before": "simplerandomiser_init"
        }
    }
],
    "Localisation": [
        "resource/simplerandomiser_localisation_%language%.txt"
    ]
}

Language file#

This follows a fairly simple template, the only thing of note is that you often get strange behaviour using UTF-8 when saving the file instead of using UTF-16 LE.

"lang"
{
    "Language" "english"
    "Tokens"
    {
        "MODE_SETTING_CATEGORY_SIMPLERANDOMISER" "Simple Randomiser"
        "SIMPLERANDOMISER" "Randomise"
    }
}

Name this file simplerandomiser_localisation_english.txt and place it in the yourmodsname/mod/resource/ folder.

Shared functions#

Let’s begin the process by first creating the file sh_gamemode_simplerandomiser.nut and making the core components of the gamemode, which is to define the gamemode properties.

global function simplerandomiser_init // initializing functions
global const string GAMEMODE_SIMPLERANDOMISER = "rand"
// we want a short term to use which allows server owners to
// select our gamemode without typing the entire name
// also makes it easier for us lol

void function simplerandomiser_init()
{
    // start defining what to do before the map loads on this gamemode
    AddCallback_OnCustomGamemodesInit( CreateGamemodeRand ) // define various properties such as name, desc, so on
    AddCallback_OnRegisteringCustomNetworkVars( RandRegisterNetworkVars ) // server callbacks stuff
}

void function CreateGamemodeRand()
{
    GameMode_Create( GAMEMODE_SIMPLERANDOMISER )
    GameMode_SetName( GAMEMODE_SIMPLERANDOMISER, "#GAMEMODE_SIMPLERANDOMISER" ) // localizations will be handled later
    GameMode_SetDesc( GAMEMODE_SIMPLERANDOMISER, "#PL_rand_desc" )
    GameMode_SetGameModeAnnouncement( GAMEMODE_SIMPLERANDOMISER, "grnc_modeDesc" )
    GameMode_SetDefaultTimeLimits( GAMEMODE_SIMPLERANDOMISER, 10, 0.0 ) // a time limit of 10 minutes
    GameMode_AddScoreboardColumnData( GAMEMODE_SIMPLERANDOMISER, "#SCOREBOARD_SCORE", PGS_ASSAULT_SCORE, 2 ) // dont fuck with it
    GameMode_AddScoreboardColumnData( GAMEMODE_SIMPLERANDOMISER, "#SCOREBOARD_PILOT_KILLS", PGS_PILOT_KILLS, 2 ) // dont fuck with it
    GameMode_SetColor( GAMEMODE_SIMPLERANDOMISER, [147, 204, 57, 255] ) // dont fuck with it

    AddPrivateMatchMode( GAMEMODE_SIMPLERANDOMISER ) // add to private lobby modes

    AddPrivateMatchModeSettingEnum("#PL_rand", "rand_enableannouncements", ["#SETTING_DISABLED", "#SETTING_ENABLED"], "1")
    // creates a togglable riff whether or not we want to announce a text to the client
    AddPrivateMatchModeSettingArbitrary("#PL_rand", "rand_announcementduration", "3")
    // Creates a riff with an arbitrary numerical value for how long the announcement text remains on screen
    // These riffs can be accessed from server configs or from the private match settings screen, under the "Simple Randomiser" category


    // set this to 25 score limit default
    GameMode_SetDefaultScoreLimits( GAMEMODE_SIMPLERANDOMISER, 25, 0 )

    #if SERVER
            GameMode_AddServerInit( GAMEMODE_SIMPLERANDOMISER, GamemodeRand_Init ) // server side initalizing function
            GameMode_SetPilotSpawnpointsRatingFunc( GAMEMODE_SIMPLERANDOMISER, RateSpawnpoints_Generic )
            GameMode_SetTitanSpawnpointsRatingFunc( GAMEMODE_SIMPLERANDOMISER, RateSpawnpoints_Generic )
            // until northstar adds more spawnpoints algorithm, we are using the default.
    #elseif CLIENT
            GameMode_AddClientInit( GAMEMODE_SIMPLERANDOMISER, ClGamemodeRand_Init ) // client side initializing function
    #endif
    #if !UI
            GameMode_SetScoreCompareFunc( GAMEMODE_SIMPLERANDOMISER, CompareAssaultScore )
            // usually compares which team's score is higher and places the winning team on top of the losing team in the scoreboard
    #endif
}

void function RandRegisterNetworkVars()
{
    if ( GAMETYPE != GAMEMODE_SIMPLERANDOMISER )
            return

    Remote_RegisterFunction( "ServerCallback_Randomiser" )
    // will come in useful later when we want the server to communicate to the client
    // for example, making an announcement appear on the client
}

The comments should hopefully explain what most of everything does, but just to summarize:

  • we defined the gamemode’s name and description using a string that we will localize ourselves later.

  • we set the default scoring method, what spawnpoint algorithm to use, as well as the scoreboard size.

  • we defined server callbacks, which we will use later on in the server scripts portion of this gamemode.

Now that we’re done, name this file sh_gamemode_simplerandomiser.nut and place it in the yourmodsname/mod/scripts/vscripts/gamemodes folder.

Server-side function#

Now that we’re down with defining the gamemode, its time to focus on the component on that makes the gamemode function in-game. For this, it will be mostly handled by the server scripts, so head into _gamemode_simplerandomiser.nut to begin writing the randomizing script.

global function GamemodeRand_Init

void function GamemodeRand_Init()
{
    #if SERVER
    SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
    SetWeaponDropsEnabled( false ) // prevents picking up weapons on the ground
    AddCallback_OnPlayerRespawned( GiveRandomGun )
    #endif
}

As you may have noticed, checking if it is a server is a special case, so we use #if SERVER and #endif instead of the usual if(thing){stuff}

Now that our initial function is created, we now have the game triggering GiveRandomGun when a player spawns, but we don’t have any such function, so let’s begin creating one.

Firstly, we need to know what weapons we can equip. For this we define an array:

array<string> pilotWeapons = ["mp_weapon_alternator_smg",
                              "mp_weapon_autopistol",
                              "mp_weapon_car",
                              "mp_weapon_dmr"]

Here we have defined an array with only 4 weapons in it, you can make this list however you like but remember to separate all but the last item with a ,

Randomise function#

As we already know its going to call the function GiveRandomGun when a player respawns, let’s define that now. First we strip any existing weapons:

void function GiveRandomGun(entity player)
{
    foreach ( entity weapon in player.GetMainWeapons() )
        player.TakeWeaponNow( weapon.GetWeaponClassName() )

This iterates through each weapon (that being the primary, secondary and anti-titan weapons) and removes them individually.

Then lets give them a new, random weapon by selecting a random item from our previous array:

player.GiveWeapon( pilotWeapons[ RandomInt( pilotWeapons.len() ) ] )

Now, remember the server callback that we defined earlier in sh_gamemode_simplerandomiser.nut? Let’s put that to use. We are going to make it so the player receives an announcement whenever they have their weapons randomized.

// checks if the toggle option is set to enabled
if ( GetCurrentPlaylistVarInt( "rand_enableannouncements", 1 ) == 1 )
    Remote_CallFunction_NonReplay( player, "ServerCallback_Randomiser" ) // call the function that will be used client-side

Overall, the server script should look like this.

global function GamemodeRand_Init

void function GamemodeRand_Init()
{
    #if SERVER
    SetLoadoutGracePeriodEnabled( false ) // prevent modifying loadouts with grace period
    SetWeaponDropsEnabled( false ) // prevents picking up weapons on the ground
    AddCallback_OnPlayerRespawned( GiveRandomGun )
    #endif
}

array<string> pilotWeapons = ["mp_weapon_alternator_smg",
                              "mp_weapon_autopistol",
                              "mp_weapon_car",
                              "mp_weapon_dmr"]

void function GiveRandomGun(entity player)
{
    foreach ( entity weapon in player.GetMainWeapons() )
        player.TakeWeaponNow( weapon.GetWeaponClassName() )

    player.GiveWeapon( pilotWeapons[ RandomInt( pilotWeapons.len() ) ] )

    // checks if the toggle option is set to enabled
    if ( GetCurrentPlaylistVarInt( "rand_enableannouncements", 1 ) == 1 )
        Remote_CallFunction_NonReplay( player, "ServerCallback_Randomiser", GetCurrentPlaylistVarFloat( "rand_announcementduration", 3 ) ) // call the function that will be used client-side
}

Name this file _gamemode_simplerandomiser.nut and place it in the yourmodsname/mod/scripts/vscripts/gamemodes folder as well. Make sure to double check that all spellings are correct in your mod as everything is case-sensitive.

Client-side functions#

Lastly, for your cl_gamemode_simplerandomiser.nut, we are going to utilize the callback functions from earlier, as well as add some music to play during the gamemode.

global function ClGamemodeRand_Init
global function ServerCallback_Randomiser

void function ClGamemodeRand_Init()
{
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_INTRO, "music_mp_freeagents_intro", TEAM_MILITIA )

    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_WIN, "music_mp_freeagents_outro_win", TEAM_MILITIA )

    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_DRAW, "music_mp_freeagents_outro_lose", TEAM_MILITIA )

    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LOSS, "music_mp_freeagents_outro_lose", TEAM_MILITIA )

    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_THREE_MINUTE, "music_mp_freeagents_almostdone", TEAM_MILITIA )

    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_IMC )
    RegisterLevelMusicForTeam( eMusicPieceID.LEVEL_LAST_MINUTE, "music_mp_freeagents_lastminute", TEAM_MILITIA )
}

void function ServerCallback_Randomiser( float duration )
{
    AnnouncementData announcement = Announcement_Create( "#RAND_RANDOMIZED" )
    Announcement_SetSubText( announcement, "#RAND_RANDOMIZED_DESC" )
    Announcement_SetTitleColor( announcement, <0,0,1> )
    Announcement_SetPurge( announcement, true )
    Announcement_SetPriority( announcement, 200 ) //Be higher priority than Titanfall ready indicator etc
    Announcement_SetSoundAlias( announcement, SFX_HUD_ANNOUNCE_QUICK )
    Announcement_SetDuration( announcement, duration )
    Announcement_SetStyle( announcement, ANNOUNCEMENT_STYLE_QUICK )
    AnnouncementFromClass( GetLocalViewPlayer(), announcement )
}

What this script does is quite simple. It registers default music to play during the intro portion, when winning, drawing or losing, as well as the event when the timelimit reaches 3 minutes or 1 minute left.

Also, it also displays an announcement towards the player when they have their weapons randomized.

Localization#

“So we’re all done with the scripting stuff, right? That means we can finally run the gamemode itself!”

Technically, yes, you could. But it wouldn’t look pretty. Remember all those strings with the # symbol in front of them? We have to localize them first so it displays correctly.

Hence, open your simplerandomiser_localisation_english.txt which is located in the yourmodsname/mod/resource/ folder.

"lang"
{
    "Language" "english"
    "Tokens"
    {
            "PL_rand" "Simple Randomiser" // displays in the lobby settings
            "rand_enableannouncements" "Toggle announcements" // describe the togglable setting
            "rand_announcementduration" "Announcement duration" // describe the numerical setting
            "PL_rand_lobby" "Simple Randomiser Lobby" // displays in lobby
            "PL_rand_desc" "Your weapons are randomised! Fight and win!" // displays in the description of the gamemode in the lobby
            "PL_rand_hint" "Your weapons are randomised! Fight and win!" // displays in the scoreboard of the gamemode ingame
            "PL_rand_abbr" "RAND"
            "GAMEMODE_TBAG" "Simple Randomiser" // displays in the loading screen
            "RAND_RANDOMIZED" "Weapons Randomized" // displays in the announcement text
            "RAND_RANDOMIZED_DESC" "Fight and win!" // displays below the announcement text, as a description
    }
}

Alright, we’re finally done! However, there’s just one thing missing, which is to let the game know what maps are available for this gamemode to play on.

Maps#

We will need to create a file called playlists_v2.txt and place it in yourmodsname/keyvalues folder.

Yes, you will need to create a folder called keyvalues which is separate from the mod folder that we placed all our scripts and localization inside.

Next, inside this playlists_v2.txt, we will need to allow/disallow what maps can the gamemode be played on.

playlists
{
    Gamemodes
    {
            rand
            {
                    inherit defaults
                    vars
                    {
                            name #PL_rand
                            lobbytitle #PL_rand_lobby
                            description #PL_rand_desc
                            hint #PL_rand_hint
                            abbreviation #PL_rand_abbr
                            max_players 12
                            max_teams 2
                            classic_mp 1

                            gamemode_score_hint #GAMEMODE_SCORE_HINT_TDM
                    }
            }
    }
    Playlists
    {
            rand
            {
                    inherit defaults
                    vars
                    {
                            name #PL_rand
                            lobbytitle #PL_rand_lobby
                            description #PL_rand_desc
                            abbreviation #PL_rand_abbr
                            image ps
                            //mixtape_slot 7
                            mixtape_timeout 120
                            visible 0
                    }
                    gamemodes
                    {
                            rand
                            {
                                    maps
                                    {
                                            mp_forwardbase_kodai 1
                                            mp_grave 1
                                            mp_homestead 1
                                            mp_thaw 1
                                            mp_black_water_canal 1
                                            mp_eden 1
                                            mp_drydock 1
                                            mp_crashsite3 1
                                            mp_complex3 1
                                            mp_angel_city 1
                                            mp_colony02 1
                                            mp_glitch 1
                                            mp_lf_stacks 1
                                            mp_lf_deck 1
                                            mp_lf_meadow 1
                                            mp_lf_traffic 1
                                            mp_lf_township 1
                                            mp_lf_uma 1
                                            mp_relic02 1
                                            mp_wargames 1
                                            mp_rise 1
                                            mp_coliseum 1
                                            mp_coliseum_column 1
                                    }
                            }
                    }
            }
    }
}

There isn’t much to say here except that we enabled this gamemode to played on all maps. So if this gamemode is set to auto-rotate maps in a server, it will go from one map to the next in order. You could disable certain maps by changing the 1 to a 0.

Another thing to note is that under the Playlists tab, there is an image slot. You could change the image that displays when selecting a gamemode in the private match lobby. You can find out what the keyvalues for the other images by checking out other gamemodes in Northstar.Custom/keyvalues/playlists_v2.txt.

Closing words#

And that should be all you need in order to create a gamemode. Thanks for reading all the way to this point, and I hope you have learnt a thing or two.

If you ever have a question or two, feel free to head into the Northstar Discord and ask about in #modding-chat.

  • Revised by x3Karma#6984