This Tutorial describes how to make a simple weapon a an universal weapon mod. The mod will replace the weapon, assign it to all inventory stations, add it to every armour class, replace it in loadouts and in loadout-profiles. In the defaultproperties you just have to define the old and new weapon-class and the quantity of ammo.
After you downloaded and set up UCC, you can start making the first mod.
Skills in other programming language are recommended!
UnrealScript is similar in basic design principles to Java, so the syntax is almost like PHP, C, or JavaScript. The compiled script is stored in u-packages, that can contain an unlimited amout of classes.
To make your own package, create a directory named like the package-name in [TVDir]\source\Game (dont use special characters or spaces!). In this Tutorial we name our package 'MyPackage'. So we create a dir named 'MyPackage' and inside this, we create another dir called 'Classes'. Here we can put all our script-files with the extension '.uc'.
Now we need a script-editor. You can use Notepad, but there are better ones like Notepad++ (that I use) or WOTgreal, a integrated development environment for UnrealScript.
Inside the '[TVDir]\source\Game\MyPackage\Classes'-dir create a new file named 'MyMutator.uc', that is our first class.
In the first line of this file write the following code:
class MyMutator extends Gameplay.Mutator;
After the definition of the class, global variables used in the class have to be declared:
var string oldWeaponClassName; var class<Gameplay.Weapon> oldWeaponClass; var string newWeaponClassName; var class<Gameplay.Weapon> newWeaponClass; var int newWeaponAmmoQuantity;
After the variable declaration we can start writing our own functions ans scripts. I already said, that the two classes have to be loaded and 'stored' into the class-variables (oldWeaponClass, newWeaponClass):
function PreBeginPlay() { oldWeaponClass = class<Gameplay.Weapon>(DynamicLoadObject(oldWeaponClassName, class'Class')); newWeaponClass = class<Gameplay.Weapon>(DynamicLoadObject(newWeaponClassName, class'Class')); Super.PreBeginPlay(); }
All code inside the function PreBeginPlay() is executed before the game starts. So we load both classes, convert them to the right type (class'Gameplay.Weapon') and 'save' them in the two variables. After this we execute the PreBeginPlay-function of the super class. (This is important because functions in classes overwrites functions in super classes.)
We want the weapon to be in our quick-loadouts, so lets make a function to do that:
function ModifyPlayerProfile(Character c, string oldW, string newW) { local int i, j; local TribesGui.PlayerProfile pp; pp = TribesGui.TribesGUIController(PlayerController(c.Controller).Player.GUIController).profileManager.GetActiveProfile(); if(pp == None || oldW ~= "" || newW ~= "") return; pp.bReadOnly = true; for(i = 0; i < pp.loadoutSlots.length; i++) for(j = 0; j < pp.loadoutSlots[i].weaponClassNameList.length; j++) if(pp.loadoutSlots[i].weaponClassNameList[j] == oldW) pp.loadoutSlots[i].weaponClassNameList[j] = newW; }
Now we add the replace-funtion that replaces the weapon and assign it to all inventory stations:
function Actor ReplaceActor(Actor Other) { local int i; if (Other.IsA(Name(oldWeaponClassName))) { Other.Destroy(); return ReplaceWith(Other, newWeaponClassName); } if (Other.IsA('InventoryStationAccess')) { for(i = 0; i < InventoryStationAccess(Other).weapons.length; i++) if(InventoryStationAccess(Other).weapons[i].weaponClass == oldWeaponClass) InventoryStationAccess(Other).weapons[i].weaponClass = newWeaponClass; } return Super.ReplaceActor(Other); }
This function is executed for each actor on the map. So we have to check if the current actor's class-name is the same like oldWeaponClassName. With the function IsA we can check if this is true, but we first have to convert oldWeaponClassName from a string to the datatype name (the funtion wants a name, not a string). So if the current actor is the weapon to replace, we destroy it and replace it with the new weapon-class.
If the actor's class-name is 'InventoryStationAccess' then its the access-class for all kind of inventory stations. With the for-loop we serach in the weapon-array for the oldWeaponClass and replace it with newWeaponClass.
To the loadouts:
function string MutateSpawnLoadoutClass(Character c) { local int i, j; for(i = 0; i < c.team().combatRoleData.length; i++) for(j = 0; j < c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList.length; j++) if(c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList[j].weaponClass == oldWeaponClass) c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList[j].weaponClass = newWeaponClass; ModifyPlayerProfile(c,oldWeaponClassName,newWeaponClassName); return Super.MutateSpawnLoadoutClass(c); }
The final step is to allow weapons for the armors:
function string MutateSpawnCombatRoleClass(Character c) { local int i, j; for(i = 0; i < c.team().combatRoleData.length; i++) for(j = 0; j < c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons.length; j++) if(c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].typeClass == oldWeaponClass) { c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].typeClass = newWeaponClass; c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].quantity = newWeaponAmmoQuantity; } ModifyPlayerProfile(c,oldWeaponClassName,newWeaponClassName); return Super.MutateSpawnCombatRoleClass(c); }
Finally we give some values to the variables:
defaultproperties { oldWeaponClassName = "EquipmentClasses.WeaponSpinfusor" newWeaponClassName = "MyPackage.MySpinfusor" newWeaponAmmoQuantity = 32 }
Now we have a nice weapon mutator that replaces all kinds of weapon. The file MyMutator.uc (download here) should now look like this:
class MyMutator extends Gameplay.Mutator; var string oldWeaponClassName; var class<Gameplay.Weapon> oldWeaponClass; var string newWeaponClassName; var class<Gameplay.Weapon> newWeaponClass; var int newWeaponAmmoQuantity; function PreBeginPlay() { oldWeaponClass = class<Gameplay.Weapon>(DynamicLoadObject(oldWeaponClassName, class'Class')); newWeaponClass = class<Gameplay.Weapon>(DynamicLoadObject(newWeaponClassName, class'Class')); Super.PreBeginPlay(); } function ModifyPlayerProfile(Character c, string oldW, string newW) { local int i, j; local TribesGui.PlayerProfile pp; pp = TribesGui.TribesGUIController(PlayerController(c.Controller).Player.GUIController).profileManager.GetActiveProfile(); if(pp == None || oldW ~= "" || newW ~= "") return; pp.bReadOnly = true; for(i = 0; i < pp.loadoutSlots.length; i++) for(j = 0; j < pp.loadoutSlots[i].weaponClassNameList.length; j++) if(pp.loadoutSlots[i].weaponClassNameList[j] == oldW) pp.loadoutSlots[i].weaponClassNameList[j] = newW; } function Actor ReplaceActor(Actor Other) { local int i; if (Other.IsA(Name(oldWeaponClassName))) { Other.Destroy(); return ReplaceWith(Other, newWeaponClassName); } if (Other.IsA('InventoryStationAccess')) { for(i = 0; i < InventoryStationAccess(Other).weapons.length; i++) if(InventoryStationAccess(Other).weapons[i].weaponClass == oldWeaponClass) InventoryStationAccess(Other).weapons[i].weaponClass = newWeaponClass; } return Super.ReplaceActor(Other); } function string MutateSpawnLoadoutClass(Character c) { local int i, j; for(i = 0; i < c.team().combatRoleData.length; i++) for(j = 0; j < c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList.length; j++) if(c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList[j].weaponClass == oldWeaponClass) c.team().combatRoleData[i].role.default.defaultLoadout.default.weaponList[j].weaponClass = newWeaponClass; ModifyPlayerProfile(c,oldWeaponClassName,newWeaponClassName); return Super.MutateSpawnLoadoutClass(c); } function string MutateSpawnCombatRoleClass(Character c) { local int i, j; for(i = 0; i < c.team().combatRoleData.length; i++) for(j = 0; j < c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons.length; j++) if(c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].typeClass == oldWeaponClass) { c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].typeClass = newWeaponClass; c.team().combatRoleData[i].role.default.armorClass.default.AllowedWeapons[j].quantity = newWeaponAmmoQuantity; } ModifyPlayerProfile(c,oldWeaponClassName,newWeaponClassName); return Super.MutateSpawnCombatRoleClass(c); } defaultproperties { oldWeaponClassName = "EquipmentClasses.WeaponSpinfusor" newWeaponClassName = "MyPackage.MySpinfusor" newWeaponAmmoQuantity = 32 }
Without a weapon the weapon mutator is useless. So we create a new file named 'MySpinfusor.uc' and put the following code inside:
class MySpinfusor extends Gameplay.Spinfusor; class MySpinfusor extends Gameplay.Spinfusor; defaultproperties { localizedName = "Super Spinfusor" infoString = "This is my new Spinfusor." roundsPerSecond = 3 ammoCount = 64 ammoUsage = 1 projectileClass = Class'EquipmentClasses.ProjectileSpinfusor' projectileVelocity = 1500 projectileInheritedVelFactor = 1 // this properties are taken from 'EquipmentClasses.WeaponSpinfusor' attentionFXMaterial = Shader'FX.ScreenFindmeShader' emptyMaterials(1) = Shader'weapons.SpinfusorDialEmptyShader' fireAnimSubString = "large" firstPersonAltMesh = SkeletalMesh'weapons.HeavySpinfusor' firstPersonAltOffset = (X=-26,Y=22,Z=-18) firstPersonAltTraceExtent = (X=10,Y=20,Z=10) firstPersonAltTraceLength = 150 firstPersonTraceExtent = (X=10,Y=20,Z=10) firstPersonTraceLength = 125 thirdPersonAltStaticMesh = StaticMesh'weapons.HeavySpinfusorHeld' thirdPersonAttachmentOffset = (X=25,Y=-2,Z=5) thirdPersonStaticMesh = StaticMesh'weapons.spinfusorheld' pickupRadius = 60 CollisionHeight = 12 CollisionRadius = 35 }
This class is very simple: it just extends the Spinfusor and changes the default-properties. 'localizedName' is the name of the weapon, 'infoString' is shown in invo-hud when you want to add the weapon to your equipment. 'roundsPerSecond' defines how often the weapon can be fired in a second. 'ammoCount' is the amount of ammon and 'ammoUsage' the usage of ammo per shot. projectileVelocity is self-explanatory. 'projectileInheritedVelFactor' is the factor the character's velocity is multiplied with, before adding it to the projectileVelocity.
(If you want to make a weapon with new functions, take a look in the weapon classes in '[TVDir]\source\Game\Gameplay\Classes\Equipment\Weapon'.)
After saving both files in the '[TVDir]\source\Game\MyPackage\Classes'-folder its time to compile. Open '[TVDir]\Program\Bin_dev\UCC.ini' in your text-editor and search for the lines starting with 'EditPackages='. Under these lines, add 'EditPackages=MyPackage', so that it looks like this:
EditPackages=Core EditPackages=Engine EditPackages=IGEffectsSystem EditPackages=IGVisualEffectsSubsystem EditPackages=IGSoundEffectsSubsystem EditPackages=Editor EditPackages=UWindow EditPackages=GUI EditPackages=TVEd EditPackages=IpDrv EditPackages=UWeb EditPackages=UDebugMenu ; Tribes specific packages EditPackages=MojoCore EditPackages=MojoActions EditPackages=PathFinding EditPackages=Scripting EditPackages=AICommon EditPackages=Movement EditPackages=Gameplay ;EditPackages=TribesGui EditPackages=Tyrion EditPackages=Physics EditPackages=TribesAdmin EditPackages=TribesWebAdmin EditPackages=TribesVoting EditPackages=TribesTVClient EditPackages=TribesTVServer ; My packages EditPackages=MyPackage
If everything is fine, you should see something like that:
Now, carefully take a look into '[TVDir]\Program\Bin_dev', where you should find the file 'MyPackage.u'. This is our compiled code! Copy the file to '[TVDir]\Program\Bin'.
Coding is finished, let's test what we did. Just type the following in the commandline: "cd ..\Bin" and "TV_CD_DVD.exe mp-emerald?mutator=MyPackage.MyMutator" and press enter: