Tutorial: Creating a new mod, starting with the Instagib example

UnrealScript uses a C-style syntax, and is similar to working with C++ or Java. If you don't know a thing about Programming and Object-Oriented Programming, you'll probably be quite lost, but that's beyond the scope of this wiki.

If you don't know a thing about UnrealScript or UE3 in general, you'll want to refer to the UDN documentation and other materials on the Code homepage. You might want to start with Tutorial: Creating the "Giant Slayers" mod before coming to this one, though this is going to repeat several of things covered in that tutorial.

In this tutorial, we're going to create a brand new mod starting with the Instagib example mod. This is a mod that replaces all of Chivalry's game modes with an "Instagib" equivalent; contrast this with the Giant Slayers mod from Tutorial: Creating the "Giant Slayers" mod, which only replaces/adds a single game mode.

I'll explain what makes Instagib tick, and why it's a useful approach to start with. I'll also explain a bit about how Chivalry's script code is laid out.

Step 0: Download a good UnrealScript editor

See: UnrealScript editors

Step 1: Download Instagib's source

Mosey on over to https://github.com/BradyBrenot/chivalry-instagib. If you have a GitHub account and understand how it works (highly recommended if you're modding), fork this repository. If you'd prefer to use your own version control system, or just to work offline for now, you can use the "Download Zip" option on the right side to simply download it.

Extract the Zip you downloaded (or clone your new repository) into 

C:\Program Files (x86)\Steam\steamapps\common\chivalrymedievalwarfare\Development\Src\TheNameOfYourMod

Replacing TheNameOfYourMod with your mod's name (and adjusting the path for your own Steam Library location if necessary)

Step 2: Rename the mod

From now on, for all file paths we'll just assume we're in the C:\Program Files (x86)\Steam\steamapps\common\chivalrymedievalwarfare\Development\Src\TheNameOfYourMod directory

Rename DefaultInstagib.ini to DefaultTheNameOfYourMod.ini

Rename all of the classes\Instagib*.uc files to classes\TheNameOfYourMod*.uc
likewise, rename all of include\Instagib*.uci to include\TheNameOfYourMod*.uci
and finally, you need to use your UnrealScript editor to find/replace every instance of "InstaGib" in DefaultTheNameOfYourMod.ini, in the .uc files, and in the .uci to whatever your mod's actual name is. This would be a pain to do manually, which is why you should be using a nice editor that can do it for you in one swoop.

Once you've done this, you've got your own mod. Now what exactly is going on here?


 

What exactly is going on here? Why does Instagib look like this?

Instagib is broken up into three directories and one .ini. You'll notice that it looks different from Chivalry's own code. Why is this?

Let's jump out of Instagib and into Chivalry's script code for the moment. Depending on whether you're modding Medieval Warfare or Deadliest Warrior, you'll find this in chivalrymedievalwarfare\Development\Src\AOC or chivalrymedievalwarfare\Development\Src\CDW respectively.

Game mode, player controller, and pawn classes

There are three classes that contain a significant portion of Chivalry's functionality, and are used to drive most of the other classes.

AOCPawn and subclasses (AOCTUTPawn, CDWDuelPawn, AOCNPC_New)

AOCPawn is Chivalry's Pawn class. A Pawn in UnrealEngine is like a "chess piece." It's an actual character or other controllable entity; AOCPawn is your actual character that runs around and swings weapons at people (siege weapons are similar entities so they're also Pawns, though they don't derive from AOCPawn). A lot of what makes Chivalry what it is comes from the particulars of how AOCPawn works. Every AOCPawn exists on the server and on every client for whom the Pawn is "relevant" (can be seen, or is otherwise gameplay-relevant).

Instagib overrides AOCPawn's TakeDamage function to make every hit a one-hit-kill.

AOCPlayerController and subclasses (AOCDuelPlayerController, AOCFFAPlayerController, ...)

AOCPlayerController is Chivalry's main PlayerController class. A PlayerController is a thing that controls pawns, and represents the player's persistent presence on a server. There's one PlayerController for every player in a game, and they exist on the server and on the owner's client. A player's PlayerController is created when they join a map, and destroyed only when they disconnect or the map changes; if you die, only your Pawn is destroyed.

Instagib's PlayerController classes override ShowDefaultGameHeader so that they can show a "Welcome!" message on first spawn.

AOCGame and subclasses (AOCCTF, AOCFFA, AOCTD, AOCDuel, CDWDuel, AOCTeamObjective, ...)

AOCGame is Chivalry's main GameInfo (or Game Type or Game Mode; we use them interchangeably here) class. GameInfo classes drive the game modes. They spawn players, they determine objectives, they determine when the game is done and who won, and even determine how the game should be displayed in the server browser (yes, they probably do too much). GameInfos also define which Pawn and PlayerController classes the server will create. A GameInfo only exists on the server, and there's exactly one that exists at any time. Even the main menu has a GameInfo (of class AOCEntry).

Instagib's GameInfo classes override the function that determines which GameInfo class to load for which maps (SetGameType(...)). They're also set to spawn Instagib's own PlayerController and Pawn classes, to reduce the respawn time, and to display the mod name "Instagib" in the server browser.

`includes, and reusing Mode, Controller, and Pawn code

Now, let's take a look at Instagib's CTF PlayerController class. Why is it so small? Where's the code that prints out that "Welcome" message I mentioned earlier?

InstagibCTFPlayerController.uc
class InstagibCTFPlayerController extends AOCCTFPlayerController
    dependson(InstagibCTF);
`include(Instagib/Include/InstagibCTF.uci)
`include(Instagib/Include/InstagibPlayerController.uci)

As you might have noticed, Chivalry has a separate PlayerController, Game, and sometimes Pawn class for every game mode. If I wanted to do the same thing in every PlayerController, the naïve way to do it would be to create a separate child PlayerController for every mode and put the same code in every one. This would become a maintenance nightmare with more complex PlayerController classes as you have to make sure you make the same changes to every one of them (unless you don't want the change in question in that class). Bleh.

Instead, we use include files which contain common functionality.

The `include macro is evaluated before the code is compiled. It copies the file in question into this script before compiling this script. Using this, we can keep common code in one file that's `included from every script that needs it.

If you're just making a single, brand new game mode you don't need to do this! Instagib is doing this because it subclasses and replaces several vanilla Chivalry game modes. If, say, you wanted to just add a "Last Man Standing" mode that derives from free-for-all, you only really need one GameInfo, one PlayerController, and maybe one Pawn (see: Tutorial: Creating the "Giant Slayers" mod)

PlayerController include files

Open up

Instagib/Include/InstagibPlayerController.uci

This is the common PlayerController code that gets added to every one of Instagib's PlayerControllers. You'll see what I was describing earlier. The function

InstagibPlayerController.uci
reliable client function ShowDefaultGameHeader()
{

Exists in AOCPlayerController.uc, so when it's overridden in the Instagib classes, this overridden version is used instead. The overridden version calls its super class' version (that is, the function that's in AOCPlayerController):

InstagibPlayerController.uci
	super.ShowDefaultGameHeader();

Before then doing its own, special thing.

InstagibPlayerController.uci
	ReceiveChatMessage("",Localize("ChatMessages", "Welcome", "Instagib"),EFAC_ALL,false,false,,false);
	SetTimer(3.0f, false, 'ShowInstagibHeader');
}
 
function ShowInstagibHeader()
{
	ReceiveLocalizedHeaderText(Localize("ChatMessages", "Welcome", "Instagib"),5.0f);
}

GameMode include files

Let's look at a game mode. Open up InstagibCTF.uc.

InstagibCTF.uc
class InstagibCTF extends AOCCTF;
`include(Instagib/Include/InstagibCTF.uci)
`include(Instagib/Include/InstagibGame.uci)

The first `include you see in Instagib's PlayerControllers, Pawns, and Games looks like

InstagibCTF.uc
`include(Instagib/Include/InstagibCTF.uci)

If you open that file, you'll notice it just has

InstagibCTF.uci
`define GAMEMODE InstagibCTF

This defines the GAMEMODE macro to be InstagibCTF. This means that anywhere in code that

`{GAMEMODE}

shows up, it's replaced with InstagibCTF. We use this in a clever bit of trickery in our common Game code so that every game mode automatically knows which PlayerController and Pawn it should use. We're going to use it in Step 3 in your new mod's PlayerControllers and Pawns to do something interesting.

Open up the second include, Instagib/Include/InstagibGame.uci. This is the common GameMode code. You'll notice that at the bottom, there's

InstagibGame.uci
PlayerControllerClass=class'`{GAMEMODE}PlayerController'
DefaultPawnClass=class'`{GAMEMODE}Pawn'

Here's where that macro comes in handy. For InstagibCTF, since `{GAMEMODE} has been defined as InstagibCTF.uci, these lines are changed by the preprocessor to read

excerpt from InstagibCTF.uc after the preprocessor gets to it (during compilation)
PlayerControllerClass=class'InstagibCTFPlayerController'
DefaultPawnClass=class'InstagibCTFPawn'

Which you'll notice matches up to the PlayerController and Pawn classes we want for CTF. 


 

Step 3: Example of doing something different with pawns

Let's do a nice, silly, contrived example. Open up your main PlayerController include (Mod/Include/ModPlayerController.uci)

Add this in:

ModPlayerController.uc
exec function PlayBattleCry(int BC)
{
	`{GAMEMODE}Pawn(Pawn).PotentiallyImmolateSelf();
}

PlayBattleCry already exists in AOCPlayerController, and it's already cooked up to a key (default: C), making this easy to test.

PotentiallyImmolateSelf does not exist in AOCPawn, so we've got to add that too. Notice that we used the `{GAMEMODE} macro just like game modes do so we can cast the PlayerController's pawn to the correct type.

In the shared Pawn include (Mod/Include/ModPawn.uci) add in:

ModPawn.uci
/** Variables should be at the top of the source file **/
 
var ParticleSystem SelfImmolationPS;
 
 
/** Then come functions: **/
 
//simulated => this will run on either the server or a client (well, read the replication documentation for a better explanation)
simulated function PotentiallyImmolateSelf()
{
	//50% chance of playing a BattleCry instead of burning myself
	if(FRand() < 0.5)
	{
		PlayBattleCry(0);
	}
	else
	{
		ServerImmolateSelf();
	}
}
 
//this is a function that gets routed from the client to the server; be careful with these. Read the replication / networking documentation for more info.
unreliable server function ServerImmolateSelf()
{
	SetPawnOnFire(SelfImmolationPS, Controller, Controller, class'AOCDmgType_Lava')
}
 
/** the defaultproperties block should go at the bottom of the source file **/
defaultproperties
{
	SelfImmolationPS=ParticleSystem'CHV_PartiPack.Particles.P_fire_blazing_grow1_nosmoke'
}

What will this do? Pressing the Battlecry button gives you a 50% chance of battlecrying, and a 50% chance of burning yourself. Fun!

Step 4: Compiling, testing, cooking, uploading, and running it on a server

See: Tutorial: Compiling, testing, cooking, uploading, and running a mod

Step 5: Where to go from here

Now you have a working mod with a unique name that replaces all of Chivalry's base game modes. The sky's the limit from here.

Further reading

See Chivalry's scripts: what's what?; this document explains further about how Chivalry's code is laid out, and other things to replace to expand its functionality.

You'll also want to check out Chivalry's scripts; you can use http://sourceforge.net/projects/uncodex/ to generate a nice class hierarchy for this purpose. UnrealWiki has a useful API reference, though it doesn't include Chivalry-specific scripts, nor Chivalry-specific Engine modifications. http://wiki.beyondunreal.com/UE3:Object_(UDK)