Mostly Harmless
Legacy:Writing And Using An Embedded Mutator
- Author: Dawn
- Date: 06/27/2002
Contents
- 1 Note
- 2 Introduction
- 3 Elements of an embedded Mutator
- 4 Registering an embedded Mutator
- 5 Registering Special Mutator Types
- 6 Checking for Multiple Instances
- 7 Unlinking Special Mutator Types
- 8 Embedding a Mutator in a Map
- 9 Putting It All Together
- 10 EmbeddedMutator.int
- 11 Conclusion
- 12 Questions and Comments
Note[edit]
This page applies to Unreal Tournament for an Embedded Mutator Tutorial for UT2003/04, see Embedding Code.
Introduction[edit]
Creating a Mutator for Unreal Tournament has always been an easy way for anyone armed with a little programming experience or a couple of tutorials to modify or enhance gameplay in UT. Mutators can alter many elements of a game including weapon assortment, player characteristics, level parameters, scoring, messaging, and HUD displays, to mention a few.
The conventional Mutator is a separately compiled code file that is linked at run time to a UT map when it is loaded and played. The benefits of this arrangement are that any map can be modified at run time by simply choosing different combinations of Mutators to load, and many Mutators can be used with virtually any map. The drawbacks of this arrangement are that some maps can be created specifically for a certain Mutator whose absence at run time makes playing the game nonsensical or undesirable, and running a server unattended with a maplist usually means running all the maps in the maplist with the same set of Mutators simply because no facility exists to switch Mutators in and out within the maplist.
Another scenario arises from time to time in which a mapper may discover that his map needs the services of a Mutator, but he doesn't want to distribute the map and Mutator separately. Wouldn't it be nice in those situations if a Mutator could be designed to be embedded within a map, relieving server admins and mappers from the responsibility of keeping track of two or more separate, but essential files?
This tutorial will explain the key elements of writing and using an embedded Mutator, and demonstrate their application in a working example.
Elements of an embedded Mutator[edit]
Without some additional code of its own, an embedded Mutator will not function properly. Some sections of code, such as a PreBeginPlay(), PostBeginPlay(), etc., will run, but because Mutators are normally linked together at run time by a device called a Linked List, the embedded Mutator will not be automatically linked and will therefore miss some of the function calls it needs to perform properly.
So the first element of an embedded Mutator is some means to register, or link, itself into the Mutator Linked List.
In addition to the Mutator Linked List which the Unreal engine uses to send Mutator-specific function calls to each Mutator in the list, there are other types of Linked Lists that pertain to some Mutators: the DamageMutator, MessageMutator, and HUDMutator linked Lists. These special types of Mutators receive additional function calls if registered.
Additional code elements will be required for these special types of Mutators if they apply.
If the mapper can be content that after embedding his Mutator no other instance of the same Mutator will be accidentally loaded, either as another embedded instance in the map or an external copy loaded at runtime, no other elements may be required for the embedded Mutator. But strange things may happen including abrupt game crashes if two or more instances are loaded and run.
A robust embedded Mutator needs to contain an element of code to check for multiple instances of itself and to allow only one instance to survive before gameplay begins.
Because the first instance of an embedded Mutator can't know about any subsequent instances of itself (until it's too late), it will link itself and cause other effects that must be undone by the final instance of itself. The last instance loaded has the best opportunity to unlink previous instances from the Mutator Linked List and special Mutator Lists, so code must be provided to perform these functions.
This ability to undo effects of previous instances of itself is the final element of a general purpose embedded Mutator.
These various elements will be discussed in detail next.
Registering an embedded Mutator[edit]
Placing the code of a Mutator within a map bypasses the normal Mutator registration process that occurs in GameInfo.InitGame(). The relevant code looks like this:
while ( InOpt != "" ) { pos = InStr(InOpt,","); if ( pos > 0 ) { LeftOpt = Left(InOpt, pos); InOpt = Right(InOpt, Len(InOpt) - pos - 1); } else { LeftOpt = InOpt; InOpt = ""; } log("Add mutator "$LeftOpt); MClass = class<Mutator>(DynamicLoadObject(LeftOpt, class'Class')); BaseMutator.AddMutator(Spawn(MClass)); }
The While loop parses a string containing a list of Mutators, if any, to be loaded and linked before the start of a game. Once MClass is set to the class of a particular Mutator, the last line Spawns an instance of the Mutator and then calls BaseMutator.AddMutator(), which registers the Mutator by linking it into the Mutator Linked List.
An embedded Mutator will never receive this call since it's not in the InOpt string being parsed here, but fortunately, an alternative exists: any PreBeginPlay() or PostBeginPlay() function in the Mutator will be run automatically before the game begins and can be used to perform the necessary task. Here is sample code to accomplish this:
var bool bPreBPInitialized; function PreBeginPlay() { if ( !bPreBPInitialized ) // older versions of UT call this function twice but we // only want to run our code once! { bPreBPInitialized = True; // Add the mutator by linking it into the Mutator List ala Beppo. // This Embedded Mutator makes sense to be 1st after the BaseMutator // in the Mutator List... Self.NextMutator = Level.Game.BaseMutator.NextMutator; // Make a place in the List Level.Game.BaseMutator.NextMutator = Self; // place it 1st after BaseMutator } }
The last two lines do all the work of inserting the embedded Mutator into the Mutator Linked List just after the built-in, or base, Mutator referred to as BaseMutator. Any other Mutators already linked into the list are shoved back to make room for the embedded Mutator. Adding this code to any Mutator will allow it to be embedded in a map and subsequently run and registered when the map is played.
Registering Special Mutator Types[edit]
Damage Mutators, MessageMutators, and HUDMutators are special types of Mutators that register themselves and get Linked into their additional respective Lists by a call to one of these functions:
Level.Game.RegisterDamageMutator(Self); Level.Game.RegisterMessageMutator(Self); RegisterHUDMutator();
A Mutator of any of these types will already contain a call to one or more of these functions, so no additional effort is required for the embedded Mutator. However, a well-behaved embedded Mutator must be aware of these calls and deal with them later on.
Checking for Multiple Instances[edit]
If a mapper carelessly inserts multiple copies of a Mutator into his map, or if someone accidentally adds a copy of the same embedded Mutator at run time, the consequences may be unpleasant unless the Mutator checks for this condition and takes appropriate actions to ensure that only one copy survives when a game begins. A good place to perform this function is in the same PreBeginPlay() where the embedded Mutator already registered itself. An improved PreBeginPlay() looks like this:
function PreBeginPlay() { local Mutator M, Previous, Temp; if ( !bPreBPInitialized ) // older versions of UT call this function twice but we // only want to run our code once! { bPreBPInitialized = True; // Add the mutator by linking it into the Mutator List ala Beppo. // this Embedded Mutator makes sense to be 1st after the BaseMutator // in the Mutator List... // But first, check for and destroy an already loaded version for (M = Level.Game.BaseMutator; M != None; M = M.NextMutator) { if (GetItemName(string(M.class)) == GetItemName(string(Self.class))) //Found Self or a copy? { Previous.NextMutator = M.NextMutator; // Unlink from the Mutator List if (M != Self) // Not Self - this is a copy! { M.Destroy(); } } else // this will happen at least the 1st time thru the For Loop. { Previous = M; // So Previous can now be used as the Previous Mutator // in the next pass thru the for Loop. } } Self.NextMutator = Level.Game.BaseMutator.NextMutator; // Make a place in the List Level.Game.BaseMutator.NextMutator = Self; // place it 1st after BaseMutator } }
The for loop steps through the current list of Mutators, looking for Mutators of the same class, and if any are found, a check is made to see if they are copies. GetItemName() is used to remove the Parent Class from the class name because copies of the same class can have different Parent Classes. If a copy is found, the Mutator Linked List is adjusted to unregister the copy and then the copy is destroyed with a call to Destroy(). Only the last instance of the Mutator that runs this code will survive and reregister itself.
There is a situation where the Mutator's PreBeginPlay() will run and AddMutator() can be called on it. In that case, the Mutator would be registered by it's own PreBeginPlay() and then again by GameInfo.InitGame(). Although only one copy of the code exists, it will be registered twice and all Mutator function calls will get passed to this Mutator twice. The following function redefines AddMutator() and checks if the call is to this Mutator. If so, no registration is performed, otherwise super.AddMutator() performs the normal registration:
// We already added ourselves to the Mutator List in our PreBeginPlay, but // GameInfo.InitGame() will erroneously add us again unless we intercept the // call with our own AddMutator function and prevent it. function AddMutator(Mutator M) { if (M == Self) { return; // Don't add us. } super.AddMutator(M); // keep the chain unbroken }
Unlinking Special Mutator Types[edit]
If the embedded Mutator is also registered as a special Mutator type such as a DamageMutator, it should have been noted already so that steps can be taken to unregister any copies before they are destroyed. The final version of this example PreBeginPlay() function contains a call to RegisterDamageMutator() so that a method can be shown to unlink any copies from the DamageMutator Linked List. A function called UnLinkDamageMutator() will be created to handle this task and called from PreBeginPlay():
function PreBeginPlay() { local Mutator M, Previous, Temp; if ( !bPreBPInitialized ) // older versions of UT call this function twice but we // only want to run our code once! { bPreBPInitialized = True; // Add the mutator by linking it into the Mutator List ala Beppo. // this Embedded Mutator makes sense to be 1st after the BaseMutator // in the Mutator List... // But first, check for and destroy an already loaded version for (M = Level.Game.BaseMutator; M != None; M = M.NextMutator) { if (GetItemName(string(M.class)) == GetItemName(string(Self.class))) //Found Self or a copy? { Previous.NextMutator = M.NextMutator; if (M != Self) // Not Self - this is a copy! { // Unlink from DamageMutator List BEFORE destroying this version. We // would need to call a similar function to UnLink from MessageMutator // and HUDMutator Lists if we were registered as them. UnLinkDamageMutator(M); // do this BEFORE M.Destroy()! M.Destroy(); } } else // this will happen at least the 1st time thru the For Loop. { Previous = M; // So Previous can now be used as the Previous Mutator // in the next pass thru the for Loop. } } Self.NextMutator = Level.Game.BaseMutator.NextMutator; // Make a place in the List Level.Game.BaseMutator.NextMutator = Self; // place it 1st after BaseMutator // Here's an example that illustrates how a Mutator might also register itself as a // special Mutator such as a DamageMutator: // A similar call would be made to // RegisterMessageMutator and RegisterHUDMutator if required. Level.Game.RegisterDamageMutator(Self); } } // End PreBeginPlay. //================================================================= // Finding a copy of this Mutator and destroying it in our PreBeginPlay messes // up the DamageMutator Linked List since we're registered as a DamageMutator, // so this function removes our Mutator from the List. We would need similar // functions for UnLinking from MessageMutator and HUDMutator Lists if we were // registered as them. function UnLinkDamageMutator(Mutator M) { local bool bNotFirst; local Mutator C, Previous; for (C = Level.Game.DamageMutator; C != none; C = C.NextDamageMutator) { if (C == M) { break; } bNotFirst = True; Previous = C; } if (bNotFirst) { Previous.NextDamageMutator = C.NextDamageMutator; } else { Level.Game.DamageMutator = Level.Game.DamageMutator.NextDamageMutator; } }
The call to the new function, UnLinkDamageMutator(), must be made before the Mutator copy is destroyed because a reference to M, the copy, is passed to the new function. The Linked Lists for Damage, Message, and HUD Mutators are linked in reverse order so there is no "Base" Mutator in these lists. In this example, Level.Game.DamageMutator points to the last DamageMutator to be linked to the list instead of the first, so it's rather dynamical and the new function contains code to deal with this. MessageMutators and HUDMutators would require similar new functions to handle their own types.
Embedding a Mutator in a Map[edit]
Once an embedded Mutator has been properly written, it can be compiled in the normal fashion to create a <MutatorName>.u file (replace <MutatorName> with actual file name) that can be added to a map in the following manner:
- Make sure this Mutator is NOT listed in the "EditPackages" section of your UnrealTournament.ini file!
- Make sure <MutatorName>.u IS located in your UnrealTournament/System Folder.
- Start a fresh UnrealEd session and open your Map for editing.
- In the Command entry field at the bottom of the UED window type:
obj load file= <MutatorName>.u package=MyLevel
- Locate your mutator in the Actor Classes browser: Actor >> Info >> Mutator >> <MutatorName> and add it somewhere in your map. Place only ONE instance of this Mutator in your Map.
- Build and Save your map – the embedded Mutator code with your settings will be saved away in the map.
If you subsequently delete the single instance of the Mutator from your map, then re-Build and re-Save, the Mutator code will be deleted from the map and you will have to repeat all these steps to embed it again. (See MyLevel and Embedding Code for more on this)
Note: For UT2003 and UT2004, in order to complete step 5 and actually add the mutator actor into your map, you need to specify the mutator as placeable. This is easily achieved by adding the command word when you define the class as below:
class MutEmbeddedTest extends Mutator placeable;
Putting It All Together[edit]
The following, heavily commented, code is a complete embedded Mutator developed for a specific map. The map is a DeathMatch type that pits players against each other in long, narrow hallways with only the Chainsaw and the ImpactHammer to start out with. The player's normal Enforcers are replaced by Chainsaws whenever the players spawn. The map may contain other weapons including Enforcers lying around to be picked up, but the players get no Enforcers when they spawn. Additionally, the damage that Bots can inflict with Chainsaws and Hammers is reduced because they have a distinct advantage with those weapons in the narrow hallways. Therefore this Mutator is registered as a DamageMutator so that it can receive calls to MutatorTakeDamage().
//============================================================================= // EmbeddedMutator. // *** for use as an Embedded or Conventional Mutator *** // Replaces the Player's Enforcer with the Chainsaw or other weapon // An Embedded Mutator for placing INSIDE a UT Map or // can also be used in the conventional manner. // Author: Dawn, with much assistance from Norbert Bogenrieder aka Beppo // Tested only on version 4.36 // Version 1.0 06/24/2002 10:19:56 AM //============================================================================= // To embed this Mutator in a Map: // 1. Make sure this Mutator is NOT listed in the "EditPackages" section of your // UnrealTournament.ini file! // 2. Make sure "EmbeddedMutator.u" IS located in your UnrealTournament/System // Folder. // 3. Start a fresh UED session and open your Map for editing. // 4. In the Command entry field at the bottom of the UED window type: // // obj load file=EmbeddedMutator.u package=MyLevel // // 5. Open the Actor class Browser and scroll down to find // // Actor->Info->Mutator->EmbeddedMutator // // 6. Highlight "EmbeddedMutator" in the browser and then select a surface // somewhere in your map, right-click on the surface, and in the pop-up // menu that appears, choose "Add EmbeddedMutator Here". Place only ONE // instance of this Mutator in your Map. // 7. The resulting Actor that is placed in your map can be double-clicked on // to bring up the "EmbeddedMutator Properties" browser where you can select // the Enforcer Replacement weapon, the Bot Rating, and more. // 8. Build and Save your map - the embedded Mutator code with your settings // will be saved away in the map. // // If you subsequently delete the single instance of the Mutator from your map, // and then reBuild and reSave, the Mutator code will be deleted from the map // and you will have to repeat all these steps to embed it again. //============================================================================= class EmbeddedMutator extends Mutator config(EmbeddedMutator); var bool bPreBPInitialized, bPostBPInitialized; var float BotFactor; // Relative damage that Bots inflict compared to Players var string TheReplacement; // Weapon that will replace Enforcer var DeathMatchPlus DM; enum EBotRating // Damage that Bots give Players { Poor, // quarter damage Fair, // half the damage Good, //three quarter damage EqualToPlayers // full damage }; enum ERWeaponType // Replacement Weapon type. { Chainsaw, Enforcer, DoubleEnforcer, ImpactHammer, Minigun2, PulseGun, Ripper, ShockRifle, SuperShockRifle, SniperRifle, UT_BioRifle, UT_Eightball, UT_FlakCannon, Redeemer }; // Variables which the Level designer can adjust: var() config bool bDebug; // Enable some logging to the log file var() config ERWeaponType EnforcerReplacement; // accessible to Level Designer var() config EBotRating BotRating; // accessible to Level Designer var() config bool bUseBotRatingOnMeleeWeapsOnly; // Which Weapons will Bots inflict less damage with // PreBeginPlay will be used mainly to Bootstrap load this Mutator by linking it // into the Mutator List as the 1st Mutator in a possible chain of Mutators. function PreBeginPlay() { local int i; local Mutator M, Previous, Temp; if (bDebug) { Log("**** Entered EmbeddedMutator PreBeginPlay"); Log("**** And bPreBPInitialized is "$string(bPreBPInitialized)); } if ( !bPreBPInitialized ) // older versions of UT call this function twice but we // only want to run our code once! { bPreBPInitialized = True; // Add the mutator by linking it into the Mutator List ala Beppo. // this Embedded Mutator makes sense to be 1st after the BaseMutator // in the Mutator List... // But first, check for and destroy an already loaded version for (M = Level.Game.BaseMutator; M != None; M = M.NextMutator) { if (GetItemName(string(M.class)) == GetItemName(string(Self.class))) // Found Self or a copy? { Previous.NextMutator = M.NextMutator; if (M != Self) // This is the other version { // Unlink from DamageMutator List BEFORE destroying this version. We // would need to call a similar function to UnLink from MessageMutator // and HUDMutator Lists if we were registered as them. UnLinkDamageMutator(M); // do this BEFORE M.Destroy()! M.Destroy(); } } else // this will happen at least the 1st time thru the For Loop. { Previous = M; // So Previous can now be used as the Previous Mutator // in the next pass thru the for Loop. } } // We need to Bootstrap this instance into the List Self.NextMutator = Level.Game.BaseMutator.NextMutator; // Make a place in the List Level.Game.BaseMutator.NextMutator = Self; // place it 1st after BaseMutator if (bDebug) Log("**** Mutator "$string(Self)$" is added to the list"); // Now register our self as a DamageMutator. A similar call would be made to // RegisterMessageMutator and RegisterHUDMutator if required. Level.Game.RegisterDamageMutator(Self); BotFactor = GetBotRating(); // How much will we modify Bot-inflicted damage? TheReplacement = GetRWeaponType(); //What's the Enforcer replacement weapon? DM = DeathMatchPlus(Level.Game); if (bDebug) { LogMutatorInfo(); Log("**** Leaving EmbeddedMutator PreBeginPlay"); } } } // End PreBeginPlay: Our Mutator is linked into the Mutator List // Write some informative stuff to the log function LogMutatorInfo() { local Mutator M; Log("**** Current MutatorList:"); for (M = Level.Game.BaseMutator; M != None; M = M.NextMutator) { Log("**** "$string(M.class)); } Log("**** Current DamageMutatorList:"); for (M = Level.Game.DamageMutator; M != none; M = M.NextDamageMutator) { Log("**** "$string(M.Class)); } } // Finding a copy of this Mutator and destroying it in our PreBeginPlay messes // up the DamageMutator Linked List since we're registered as a DamageMutator, // so this function removes our Mutator from the List. We would need similar // functions for UnLinking from MessageMutator and HUDMutator Lists if we were // registered as them. function UnLinkDamageMutator(Mutator M) { local bool bNotFirst; local Mutator C, Previous; for (C = Level.Game.DamageMutator; C != none; C = C.NextDamageMutator) { if (C == M) { break; } bNotFirst = True; Previous = C; } if (bNotFirst) { Previous.NextDamageMutator = C.NextDamageMutator; } else { Level.Game.DamageMutator = Level.Game.DamageMutator.NextDamageMutator; } } //============================================================================== // We already added ourselves to the Mutator List in our PreBeginPlay, but // GameInfo.InitGame() will erroneously add us again unless we intercept the // call with our own AddMutator function and prevent it. function AddMutator(Mutator M) { if (M == Self) { return; // Don't add us. } super.AddMutator(M); // keep the chain unbroken } function float GetBotRating() { switch( BotRating ) // accessible to Level Designer { case Poor: return 0.25; case Fair: return 0.5; case Good: return 0.75; case EqualToPlayers: return 1.0; default: return 0.5; } } function string GetRWeaponType() { switch( EnforcerReplacement ) // accessible to Level Designer { case Chainsaw: return "Botpack.Chainsaw"; case Enforcer: return "Botpack.Enforcer"; case DoubleEnforcer: return "Botpack.DoubleEnforcer"; case ImpactHammer: return "Botpack.ImpactHammer"; case Minigun2: return "Botpack.Minigun2"; case PulseGun: return "Botpack.PulseGun"; case Ripper: return "Botpack.Ripper"; case ShockRifle: return "Botpack.ShockRifle"; case SuperShockRifle: return "Botpack.SuperShockRifle"; case SniperRifle: return "Botpack.SniperRifle"; case UT_BioRifle: return "Botpack.UT_BioRifle"; case UT_Eightball: return "Botpack.UT_Eightball"; case UT_FlakCannon: return "Botpack.UT_FlakCannon"; case Redeemer: return "Botpack.WarheadLauncher"; default: return "Botpack.Chainsaw"; } } // Called just before PlayerPawn is Spawned. We use it to adjust a Player's // Inventory to our liking function ModifyPlayer( Pawn PlayerPawn ) { local Inventory MyEnforcer; MyEnforcer = PlayerPawn.FindInventoryType(class'Enforcer'); if (MyEnforcer != None) { MyEnforcer.DropInventory(); // Remove it from Player's Inventory MyEnforcer.Destroy(); // Remove it from the game } if ( DM == None ) // Who knows, it might happen ... return; DM.GiveWeapon(PlayerPawn,TheReplacement); // Give Replacement for Enforcer if ( NextMutator != None ) NextMutator.ModifyPlayer(PlayerPawn); } // We call MutatorTakeDamage because we have found that Bots over-excel in // Melee-only weapons maps. We want to reduce the damage Bots inflict by a // controlled amount. function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation, out Vector Momentum, name DamageType) { // Check that InstigatedBy is still a valid Bot - Bots can disappear between // the time this function is called and the time it is run! if ( InstigatedBy != none && InstigatedBy.IsA('Bot') ) { if (bUseBotRatingOnMeleeWeapsOnly) { if (InstigatedBy.Weapon != None && InstigatedBy.Weapon.bMeleeWeapon) { AlterDamage(ActualDamage); if (bDebug) Log("**** Melee Only Damage adjusted for Bot's "$GetItemName(string(InstigatedBy.Weapon.class))); } } else { AlterDamage(ActualDamage); if (bDebug) Log("**** Damage adjusted for Bot's "$GetItemName(string(InstigatedBy.Weapon.class))); } } // Give any other DamageMutators in the chain their chance at it if ( NextDamageMutator != None ) { if (bDebug) Log("**** Another DamageMutator is being called!"); NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType ); } } function bool AlterDamage(out int ActualDamage) { if (bDebug) Log("Potential Bot-inflicted Damage: "$ActualDamage$" "$BotFactor); ActualDamage*=BotFactor; // Adjust the Bot's Damage if (bDebug) { Log("***Actual Bot-inflicted Damage: "$ActualDamage); Log(""); } return true; } // End: Players get a Replacement for Enforcer when Spawning // and Bots inflict less Damage if desired. defaultproperties { bDebug=False BotRating=Fair // An empirically determined default for Chainsaw Matches bUseBotRatingOnMeleeWeapsOnly=True EnforcerReplacement="Botpack.Chainsaw" }
In the event that the above code is to be tested or used as a conventional, non-embedded Mutator, the following associated .int and .ini text is provided:
EmbeddedMutator.int[edit]
[Public] Object=(Name=EmbeddedMutator.EmbeddedMutator,Class=Class,MetaClass=Engine.Mutator, Description="EmbeddedMutator,Replace Player's Enforcer with Chainsaw")
–EmbeddedMutator.ini –
[EmbeddedMutator.EmbeddedMutator] bDebug=False BotRating=Fair bUseBotRatingOnMeleeWeapsOnly=True EnforcerReplacement="Botpack.Chainsaw"
Conclusion[edit]
A method for embedding a Mutator in a UT map has been described and sample code, including a fully debugged example, has been provided. This method is useful for mappers who want to permanently alter gameplay within a particular map without the burden of requiring a separate Mutator to be maintained, and for adventurous server admins who want to control which Mutators are used on a map-by-map basis in a map list.
Dawn <arkdesign@psln.com>
Questions and Comments[edit]
Legal: Great job! So, with this I can, say, double all damage then? Is something like it in there already, I'm not sure... :hmm:
Mosquito: Wow. This is so useful, thanks Dawn. You should post this up over at The Wavelength. More people can read it then.
StrikeFerret: Did I miss it, or didn't this cover, say, embedding it in a GameType instead of a Map?
Wormbo: To always use a mutator in your gametype just spawn and add it to the list somewhere in the GameInfo's initialization functions, preferably in InitGame()
, where all other mutators are created, too. You could also extend DMMutator, add your mutator's functionality there and use it as your gametype's base mutator class.
Reaper_Monkey: Okay how can i make a mutator only apply to a zone on a map? as this says i can only have one.. so i assume its global for all of the map? And i want to have a mutators affect only in an area.. any ideas?
Sweavo: Nice! Reading the first paragraph reminded me of http://mapmixer.oceaniaut.com/ that StarWeaver told me about. I've not tried it, but it's worth checking out and possibly rolling into this page
Xian: Reaper_Monkey, try checking the Player zone, for example if you want to get double damage in a zone called "Battle Arena", just do something like this:
function MutatorTakeDamage (out int Damage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation, out Vector Momentum, name DamageType) { Super.MutatorTakeDamage(Damage,Victim,InstigatedBy,HitLocation,Momentum,DamageType); if ((InstigatedBy == None) || (InstigatedBy.PlayerReplicationInfo == None)) return; if (((InstigatedBy.PlayerLocation != None) && (InstigatedBy.PlayerLocation.LocationName != "Battle Arena")) && ((InstigatedBy.PlayerZone != None) && (InstigatedBy.PlayerZone.ZoneName != "Battle Arena"))) return; Damage *= 2; }
Not tested, but should be good. Note that it won't work if you call it "BaTTle ArENa" (as you have probably guessed)
Rendroc: Why does the embedded Mutator's PreBeginPlay() sample code manually insert the Mutator into the linked list rather than just calling Level.Game.AddMutator(String(Self.Class))?
Wormbo: AddMutator() spawns a new mutator instance, so afterwards you have two mutators: The one in the map, which is not in the mutators chain, and the one spawned by AddMutator, which is correctly registered to received mutator events.
Rendroc: Yes but would it be that difficult to disable the embedded instance? In PreBeginPlay() it could perhaps check its Owner, and if it is != Level.Game it calls AddMutator() and then exits/destroys itself, otherwise it continues. Would this work?