IM? What's an IM?
A Developer's Guide to Writing Individuals for Cosmic Unreal
Written by: Chris "kloc Thirteen" Stewart
A Pass the Mustard Production
Copyright 2000 Chris Stewart
Table of Contents
What is Cosmic Unreal and an Individual Mutator?
Why Develop an Individual Mutator?
How Cosmic Works
How an IM Works
An Example IM
Advanced IM topics
Icons
Configuration Windows
HUD
Appendix A: Individual Default Properties
Appendix B: Individual Events
Appendix C: GenericBaseConfigClient
What is Cosmic Unreal and an Individual Mutator?
Mutators are a special mod for Unreal Tournament that allow for (usually minor) changes to game play. These mutators can be used with any type of game so the same mutator can be applied to DeathMatch, Capture the Flag, any new type of game play created, etc. Mutators can apply the same changes to all players and/or the entire playing arena.
Cosmic Unreal (CU) is a special mutator created to allow for mutations to be applied to individual players or bots. This way, not all characters have to be the same. Each player or team can have a completely unique attributes or powers.
Cosmic uses a special subclass of mutators call individual mutators or IM's. One of the special features of IM's is that in addition to being able to be used with Cosmic, most can also be used as normal regular mutators. This means that a player does not need to use Cosmic in order to use your IM, but they do have to have it installed on their systems. New IM packages can be easily created and dynamically added to the CU universe.
For more information on Cosmic Unreal and the Individuals available, please point your browser to Cosmic Unreal.
This document is for those developers that wish to create their own IM's or package of IM's. Developers should be familiar with UnrealScript and developing normal mutators for Unreal Tournament.
Why develop an Individual Mutator?
Individuals give developers many benefits over a normal mutator. First of all, and most import, an IM can be applied to a single player, a team, or any sub-set of players. This increases the utility of any mutator enormously. Second, it can also work just like a normal mutator but provides a simplified programming interface to do some common things such as configuration windows, and HUD mutators.
Cosmic Unreal is a mutator that grabs all event calls and forks them to the appropriate player. The first thing CU does is watch for players entering the game. As each player enters, a handle is stored to that player and an IM is assigned and linked to that player. As normal mutator events are called they are forked to each individual player and their IM (or list of IMs).
IM's are derived from mutators. They can be chained together like mutators, and it's important that in any event call you pass the event on to the next IM in your individual list. Currently, CU only allows for one IM per player. This is more a user interface issue than an architecture issue. However, future version of CU will definitely have multiple IM's per player. Just like mutators, it is important that your IM behaves well with others.
When an Individual Mutator is used as a normal mutator it does a little fancy footwork. It spawns off a copy of itself for each player entering the game. And like Cosmic, will then fork mutator event calls to the appropriate IM. When you include the "Flash" IM as a mutator in a game with 3 players, there will be 4 "Flash" IM's in the system. One of the IMs will be the base mutator and then one "Flash" IM for each player. The base mutator will fork all mutator event calls to the individual IMs. See the diagram below:
Luckily you don't really need to know any of this because it all happens inside the guts of Cosmic Unreal and the Individual base class.
The very first thing you'll need to do is install Cosmic Unreal on your system. Second, you'll have to modify the UnrealTournament.ini file. In the [Editor.EditorEngine] section you will need to add a line that says EditPackages=Cosmic and then another line that says EditPackages=YourPackagename after the Cosmic entry. While you have the ini file open check the [Engine.GameEngine] section. There should be a single entry saying ServerPackages=Cosmic , and add another line with your package name.
As a demonstration of IM's, a new individual will be created called "Generic A". Generic will allow for the modification of speed, max health, damage, jumping ability, etc. This will allow an easy way to change the attributes of various players.
Here's the basic framework for a IM:
//============================= // Generic A //============================= class GenericA expands Individual; function ModifyIndividual() { Super.ModifyIndividual(); } function UnModifyIndividual() { Super.UnModifyIndividual(); } defaultproperties { nVersion=100 strPowerSentence="You have the power of configurability" strPowerDescription="You are completely modifiable." strPowerHistory="You are from planet X" strCreatedBy="kloc13" }
ModifyIndividual() is a similar function call as the mutator ModifyPlayer(). It performs the exact same task, and is called every time a player enters the game, or when the player re-spawns after a death. Unlike the ModifyPlayer function call, ModifyIndividual does not pass the Pawn to modify. Instead IM's contain a variable called Me. This refers to the Pawn that this individual modifies.
You will also notice that there is a matching function for ModifyIndividual called UnModifyIndividual. This allows for powers or attributes to be removed from a player if IM's are turned off, switch, reincarnated, swapped, etc.
Also notice that in each function call the Parent class of that function is also called. This is a simple way to make sure that the next IM in the chain is notified of any events. All IM events (and mutator events in general) should call the Super version of the function call in order to behave well with other mutators.
There are also some new defaultproperties. This include: nVersion which currently should always read 100, strPowerSentence which should always read "You have the power to/of _______", strPowerDescription> which is a short description of this IM's abilities, strPowerHistory which contains a short fictional description of the IM, and strCreatedBy which should contain your name or email address. See Appendix A for other individual properties.
Let's first add some code to modify the speed of the player:
//============================ // Generic A //============================ class GenericA expands Individual; var float SpeedRatio; function ModifyIndividual() { Me.GroundSpeed = SpeedRatio * Me.Default.GroundSpeed; Super.ModifyIndividual(); } function UnModifyIndividual() { Me.GroundSpeed = Me.Default.GroundSpeed; Super.UnModifyIndividual(); } defaultproperties { nVersion=100 strPowerSentence="You have the power of configurability" strPowerDescription="You are completely modifiable." strPowerHistory="You are from planet X" strCreatedBy="kloc13" SpeedRatio=1.0 }
Pretty simple. Of course it doesn't do anything exciting right now. If you changed the SpeedRatio to another number besides 1.0 you could see the effects, and this would be pretty much the exact code as the "Flash" IM. We will worry about creating a way to modify the variables inside the class later.
To enable Cosmic to add this class to it's list of available IM's you'll need to create an INT. Create a text file called YourPackage.int. Inside that file place the following two lines:
[public] Object=(Name=YourPackage.GenericA,Class=Class,MetaClass=Cosmic.Individual, Description="GenericA, Generic Player")
This will allow your IM to be used inside CU. To also make your IM available as a regular mutator also add the following line:
Object=(Name=YourPackage.GenericA,Class=Class,MetaClass=Cosmic.Mutator, Description="GenericA, Generic Player")
As you can see only one word changed. Leave this line out if you don't want players to use your IM as a regular mutator.
The first half of the description string is what will appear in the CU listbox or the mutator selection window.
Let's continue to improve GenericA:
//============================ // Generic A //============================ class GenericA expands Individual config(Generic); var config float SpeedRatio; var config float JumpRatio; var config float DamageRatio; var config int Maxhealth; function ModifyIndividual() { Me.GroundSpeed = SpeedRatio * Me.Default.GroundSpeed; Me.JumpZ = JumpRatio * Me.Default.JumpZ * Level.Game.PlayerJumpZScaling(); Me.DamageScaling = DamageRatio * Me.default.DamageScaling; Me.default.Health = MaxHealth; Me.health = MaxHealth; Super.ModifyIndividual(); } function UnModifyIndividual() { Me.GroundSpeed = Me.Default.GroundSpeed; Me.JumpZ = Me.Default.JumpZ * Level.Game.PlayerJumpZScaling(); Me.DamageScaling = Me.default.DamageScaling; Me.default.Health = 100; Super.UnModifyIndividual(); } defaultproperties { nVersion=100 strPowerSentence="You have the power of configurability" strPowerDescription="You are completely modifiable." strPowerHistory="You are from planet X" strCreatedBy="kloc13" SpeedRatio=2.0 JumpRatio=2.0 DamageRatio=2.0 MaxHealth=200 }
As you can see it's not all completely straight forward. You have to multiply JumpRatio by Level.Game.PlayerJumpZScaling(). Plus you have to set default.MaxHealth to the health that you want, and set it to a hard coded 100 when unmodifying the player. These little tricks can only be found through testing. It's recommended that you avoid using hard coded values, but instead try multiplying by scaling factors, and try to stay away from changing the default values unless there is no other solution. This will help make your IM more well behaved when used with other IM's and inside different game types.
With config specified in the class declaration and with the variables, all four variables will be placed in the Generic.ini configuration file. This provides a simple way to save and edit the default values. To create a more user friendly way to modify values, you can create a mod menu item like most other mods, or you can read the Advanced IM topics to find out how to create configuration screens inside Cosmic Unreal. It's recommended that IM's use the Cosmic Unreal configuration ability. This way, as more tools and features are created for IM's (for example: web configuration), they will enhance your IM automatically. Plus, the mod menu would become cluttered if each IM added it's own configuration menu item.
So GenericA is now a killing machine. She moves twice as fast, jumps twice as high, has twice as much health, and does twice as much damage.
To add a icon to your IM you'll need to create a 64x64 indexed pcx file. Place this file in the textures direcectory under your PackageName directory. Next add a line like the following near the top of your class file, just below the class name:
#exec TEXTURE IMPORT NAME=GenericAIcon FILE=textures\A.pcx MIPS=OFF
...and in default properties set the icon property to:
icon=Texture'YourPackage.GenericAIcon'
Individual Mutators have two important properties when it comes to creating configuration windows. The first is a boolean property called bConfigScreen. If set to true then Cosmic Unreal expects this IM to have a configuration window. This property enables the config button in the IM information window. The second important property is ConfigClientWindow which is of type class<UWindowWindow>. This property allows you to specify what client window to spawn when the config button is pressed in the IM info window. You can create your own window class filled with dialog controls and such to configure your IM. An "OK" and "Cancel" button are automatically shown at the bottom of the window.
When the config button is pressed, the function SetConfigurationWindowValues(UWindowWindow win) is called. "win" is the window of the type specified in ConfigClientWindow. In this function, you can pass the information from the IM to the window by setting the controls in the window.
If the user presses "Cancel" the window closes and nothing else happens.
If the user presses "OK" then the function GetConfigurationWindowValues(UWindowWindow win) is called. Again the win variable refers to the configuration window. In this function you will read all the controls and set the properties inside your IM. Once this is done you must return true. This will close the window. If you return false the window will not close. This allows you to check that all data entered is valid and prompt/warn the user if there's a problem.
Cosmic Unreal contains a useful configuration window called GenericBaseConfigClient. By setting bConfigScreen=true and ConfigClientWindow=class'GenericBaseConfigClient', you can use this flexible configuration window. This class contains simple functions to add and read various controls. Below is the example code for our Generic character.
function static SetConfigurationWindowValues(UWindowWindow win) { local GenericBaseConfigClient gbcc; gbcc = GenericBaseConfigClient(win); gbcc.AddSlider("Health:", 1, 200, default.MaxHealth, 1); gbcc.AddSlider("Speed Ratio:", 0.05, 3.05, default.SpeedRatio, 0.1); gbcc.AddSlider("Damage Ratio:", 0.0, 10.0, default.DamageRatio); gbcc.AddSlider("Jump Ratio:", 0.0, 3.0, default.JumpRatio); } function static bool GetConfigurationWindowValues(UWindowWindow win) { local GenericBaseConfigClient gbcc; local int i; gbcc = GenericBaseConfigClient(win); default.MaxHealth = gbcc.GetSliderValue(0); default.SpeedRatio = gbcc.GetSliderValue(1); default.DamageRatio = gbcc.GetSliderValue(2); default.JumpRatio = gbcc.GetSliderValue(3); return true; } defaultproperties { nVersion=100 strPowerSentence="You have the power of configurability" strPowerDescription="You are completely modifiable." strPowerHistory="You are from planet X" strCreatedBy="kloc13" SpeedRatio=2.0 JumpRatio=2.0 DamageRatio=2.0 MaxHealth=200 bConfigScreen=true ConfigClientWindow=class'GenericBaseConfigClient' }
This will create a fairly plain window with 4 slider controls. See Appendix C for more information on the GenericBaseConfigClient class.
Creating a HUD mutator for Cosmic Unreal is fairly simple. First there's a property called bHUDMutator. Set this to true if your IM wants to modify the HUD. Everytime the HUD is drawn the function PostRenderPlayer(Canvas C, Playerpawn p) is called. The variable C is the Canvas you can draw to and the variable p refers to the player this mutator is modifying (same as Me). Also available is the variable OwnerHUD which is automatically set to the HUD that this mutator is going to modify.
It's important to note that this function will be run on the client side of the game. Which means, that it will be your responsibility to ensure that any important information used by your HUD mutator is correctly replicated to the client side. For example: if you set a variable X=5 inside ModifyIndividual, you will not be able to print the contents of X on the HUD unless you specify from the contents of X to be tranfered to the client. This can be solved in a fashion similar to the following:
replication { reliable if (Role==Role_Authority) X; }
Also remember to set the property set the variable bHumanOnly to the correct value. Bots don't have HUDs, never mind the fact that they can not use any of the extra information. So if your IM only modifies the HUD be sure to set bHumanOnly to true.
Appendix A: Individual Default Properties
int nVersion
In this version it must always read 100.
Bool bHumanOnly
True if this power only effects human players. For example this is true for IM's that modify the HUD or adds items that the bot AI has no concept of using. False if this power can be used by bot and human alike. Default: false.
Bool bConfigScreen
True if this individual has a special configuration window. If this is true the individual should set ConfigClientWindow> to the proper window class. False if there is no configuration screen for this IM. Default: false.
Class<UWindowWindow> ConfigClientWindow
The class of the window to display when the IM configuration window is brought up. This is only important if bConfigScreen is set to true. Default: class'Cosmic.IMBlankClientWindow' - this just displays an error message since this should always be overwritten.
Bool bGetDamageNotification
True if this individual wants to receive MutatorTakeDamage event calls. You DO NOT call RegisterDamageMutator in an IM, even if it's suppose to act like a regular mutator. False> if you do not want the MutatorTakeDamage function called. This is done for speed purposes. Default: false.
Bool bHUDMutator
Specifies if this IM modifies the players HUD. If so the function PostRenderPlayer(Canvas C, Playerpawn p) is called. This function is called on the client side and any important info that the HUD mutator uses must be replicated from the server. See the section on HUD IMs for more details. Default: false.
Texture Icon
This is set to the icon or image associated with this IM. This is what appears on the HUD and what appears in the IM info window. Default : Texture'Cosmic.BlankIndividual'
String strPowerSentence
Should be set to "You have the power to ___" or "You have the power of ___". Fill in the blank with a simple word or two describing the individual's power. It should not be the same as your IM's name. Default: "You have the power to ___"
String strPowerDescription
A few sentences describing how this individual effects a player. Please make sure this string fits inside the IM info window. Default: "Unknown."
String strPowerHistory
A few sentences describing the history and past of this Individual. This is only for humour or background purposes and does not need to be filled in. Again please make sure this fits in the IM info window. Default: "Unknown."
String strCreatedBy
Should contain the name/email of the developer. This info is not currently used, but it will be displayed in the next version of Cosmic Unreal. Default: "Anonymous"
ModifyIndividual()
Similar to the mutator version of ModifyPlayer. This will be called every time that a player is spawned, including when the player restarts after dying.
Return: none
UnModifyIndividual()
This is almost the logical bookend to ModifyIndividual. This function will remove the mutations effects on this player.
Return: none
ScoreKill(Pawn Killer, Pawn Other)
Same as the Mutator version of the function. It's called whenever anybody get's killed, even if it doesn't directly involve you.
Return: none
MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation, out Vector Momentum, name DamageType)
Same as the mutator version of the function. It's called for ALL damage, not just the damage involving the IM. Note that unlike the mutator version you DO NOT call level.game.RegisterDamageMutator(self). Instead you set the attribute bGetDamageNotification to true.
Return: none
SetConfigurationWindowValues(UWindowWindow win)
This function is called when the user presses the "Config" button in the IM info window. This is only possible if the bConfigScreen property is set to true. The parameter win will be an instance of the window class specified by the ConfigClientWindow property. You'll want to re-cast this variable and then set any of the window controls inside the window to reflect the state of the IM configuration data.
Return: none
GetConfigurationWindowValues(UWindowWindow win)
This function is only called if the user presses the "OK" button after accessing an IM's configuration screen. From here you can check for the validity of the entered data and store the information entered inside the IM.
Return: true if the data is valid. This will close the configuration window. False if the data is invalid. It is recommended that you show an error message and explain to the user why the data is not acceptable. The window will not be closed if false is returned.
PostRenderPlayer(Canvas C, Playerpawn p)
This function is called only if bHUDMutator is set to true. This allows the IM to draw extra information on the player's HUD. Note that in a network game this function is called on the client and any needed information must be replicated from the server.
Return: none
Appendix C: GenericBaseConfigClient
GenericBaseConfigClient is a simple dialog client window that allow for the easy addition of various dialog controls. If you don't want to subclass a dialog window, populate it with controls, and handle window messages just to read in a few little numbers, then GBCC might be for you. Don't expect anything beautiful or fancy, but it works and gives your users a simple way to configure values without opening an INI file.
You first want to set the following in DefaultProperties:
bConfigScreen=true
ConfigClientWindow=class'Cosmic.GenericBaseConfigClient'
Next look at the following example code:
function static SetConfigurationWindowValues(UWindowWindow win) { local GenericBaseConfigClient gbcc; gbcc = GenericBaseConfigClient(win); gbcc.AddLabel("Test Configuration"); gbcc.AddSlider("Slide Me:", 0.05, 3.05, default.FloatOne, 0.1); gbcc.AddEditControl("Edit Me(#):", default.FloatTwo, true, true); gbcc.AddSlider("More Sliding:", 0, 200, default.FloatThree); gbcc.AddCheckbox("Check Me:", default.bFlip); gbcc.AddEditControl("Edit Me($):", default.StringOne); } function static bool GetConfigurationWindowValues(UWindowWindow win) { local GenericBaseConfigClient gbcc; local int i; gbcc = GenericBaseConfigClient(win); default.FloatOne = gbcc.GetSliderValue(0); default.FloatTwo = float(gbcc.GetEditControlValue(0)); default.FloatThree = gbcc.GetSliderValue(1); default.bFlip = gbcc.GetCheckBoxValue(0); default.StringOne = gbcc.GetEditControlValue(1); return true; }
And that's about it. Pretty simple. It will automatically create the following window:
All controls will be added from top to the bottom and will span across the entire window. When you read the values, you pass the index number of the control you want to read. This index number is separate for each control.
You may notice that there's a small bug in version 200 of CU and that the Edit box labels do not appear. This will be fixed in a future version.
Here's a breakdown of the functions the GBCC supports:
AddLabel(string name, optional int FontIndex)
Adds a uneditable text label to the config window. This is just to display any extra information that's needed.
AddSlider(string name, float MinValue, float MaxValue, optional float DefaultValue, optional int step)
Will add a slider control with a range of values between MinValue and MaxValue. If DefaultValue is not specified the slider will be set at the halfway point. Step specifies how much to increase or decrease the slider when it is clicked in either direction.
float GetSliderValue(int index)
Will return the value of the slider in the index position.
AddCheckbox(string name, bool bOn)
Adds a checkbox with the string label specified by name. The variable bOn specifies if the check box should be checked.
bool GetCheckBoxValue(int index)
Will return the value of the checkbox in the index position.
AddEditControl(string name, string DefaultString, optional bool bNumericOnly, optional bool bFloatOnly)
Adds an edit control with the text string name as a label. DefaultString specifies what should be the default in the edit box. The variable >bNumericOnly specifies if non-numbers should be screened from being entered. If bFloatOnly is true then decimal numbers can also be added (bNumericOnly should also be set to true).
string GetEditControlValue(int index)
Returns the string specified in the index edit control.
Don't use UnrealEdit. Find a good text editor and use ucc to compile.
Without UnrealEdit you lose the class tree. Get Meltdown's UClasses from http://www.planetunreal.com/unrealtower.
Be sure to always call the Super version of IM functions.
Do NOT call RegisterDamageMutator()
Use GenericBaseConfigClient for a simple configuration window.
Make sure your UnmodifyIndividual actually un-modifies the individual. Don't leave left over side effects from hanging around and messing up a player.
Export other IM packages and take a look at more sample code.
Seek out help at the official Cosmic IM development site and post questions on the Developer's Message Board: