Announcement

Collapse
No announcement yet.

Mutator that adds custom weapon to existing UT3 maps

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Mutator that adds custom weapon to existing UT3 maps

    Hi

    Can anyone help me here, I am stuck? I want to create a mutator that adds my custom weapon pickup factory to existing UT3 maps like Arsenal or Biohazard. This is what I want to do.

    Change this mutator file code below UTMutator_Ripper to add the Ripper weapon instead of replace with other weapon pickup factory:

    Code:
    class UTMutator_Ripper extends UTMutator;
    
    struct ReplacementInfo
    {
    	/** class name of the weapon we want to get rid of */
    	var name OldClassName;
    	/** fully qualified path of the class to replace it with */
    	var string NewClassPath;
    };
    
    var array<ReplacementInfo> WeaponsToReplace;
    var array<ReplacementInfo> AmmoToReplace;
    
    function PostBeginPlay()
    {
    	local UTGame Game;
    	local int i, Index;
    
    	Super.PostBeginPlay();
    
    	// replace default weapons
    	Game = UTGame(WorldInfo.Game);
    	if (Game != None)
    	{
    		for (i = 0; i < Game.DefaultInventory.length; i++)
    		{
    			if (Game.DefaultInventory[i] != None)
    			{
    				Index = WeaponsToReplace.Find('OldClassName', Game.DefaultInventory[i].Name);
    				if (Index != INDEX_NONE)
    				{
    					if (WeaponsToReplace[Index].NewClassPath == "")
    					{
    						// replace with nothing
    						Game.DefaultInventory.Remove(i, 1);
    						i--;
    					}
    					Game.DefaultInventory[i] = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
    				}
    			}
    		}
    
    		if (Game.TranslocatorClass != None)
    		{
    			Index = WeaponsToReplace.Find('OldClassName', Game.TranslocatorClass.Name);
    			if (Index != INDEX_NONE)
    			{
    				if (WeaponsToReplace[Index].NewClassPath == "")
    				{
    					// replace with nothing
    					Game.TranslocatorClass = None;
    				}
    				else
    				{
    					Game.TranslocatorClass = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
    				}
    			}
    		}
    	}
    }
    
    function bool CheckReplacement(Actor Other)
    {
    	local UTWeaponPickupFactory WeaponPickup;
    	local UTWeaponLocker Locker;
    	local UTAmmoPickupFactory AmmoPickup, NewAmmo;
    	local int i, Index;
    	local class<UTAmmoPickupFactory> NewAmmoClass;
    
    	WeaponPickup = UTWeaponPickupFactory(Other);
    	if (WeaponPickup != None)
    	{
    		if (WeaponPickup.WeaponPickupClass != None)
    		{
    			Index = WeaponsToReplace.Find('OldClassName', WeaponPickup.WeaponPickupClass.Name);
    			if (Index != INDEX_NONE)
    			{
    				if (WeaponsToReplace[Index].NewClassPath == "")
    				{
    					// replace with nothing
    					return false;
    				}
    				WeaponPickup.WeaponPickupClass = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
    				WeaponPickup.InitializePickup();
    			}
    		}
    	}
    	else
    	{
    		Locker = UTWeaponLocker(Other);
    		if (Locker != None)
    		{
    			for (i = 0; i < Locker.Weapons.length; i++)
    			{
    				if (Locker.Weapons[i].WeaponClass != None)
    				{
    					Index = WeaponsToReplace.Find('OldClassName', Locker.Weapons[i].WeaponClass.Name);
    					if (Index != INDEX_NONE)
    					{
    						if (WeaponsToReplace[Index].NewClassPath == "")
    						{
    							// replace with nothing
    							Locker.ReplaceWeapon(i, None);
    						}
    						else
    						{
    							Locker.ReplaceWeapon(i, class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class')));
    						}
    					}
    				}
    			}
    		}
    		else
    		{
    			AmmoPickup = UTAmmoPickupFactory(Other);
    			if (AmmoPickup != None)
    			{
    				Index = AmmoToReplace.Find('OldClassName', AmmoPickup.Class.Name);
    				if (Index != INDEX_NONE)
    				{
    					if (AmmoToReplace[Index].NewClassPath == "")
    					{
    						// replace with nothing
    						return false;
    					}
    					NewAmmoClass = class<UTAmmoPickupFactory>(DynamicLoadObject(AmmoToReplace[Index].NewClassPath, class'Class'));
    					if (NewAmmoClass == None)
    					{
    						// replace with nothing
    						return false;
    					}
    					else if (NewAmmoClass.default.bStatic || NewAmmoClass.default.bNoDelete)
    					{
    						// transform the current ammo into the desired class
    						AmmoPickup.TransformAmmoType(NewAmmoClass);
    						return true;
    					}
    					else
    					{
    						// spawn the new ammo, link it to the old, then disable the old one
    						NewAmmo = AmmoPickup.Spawn(NewAmmoClass);
    						NewAmmo.OriginalFactory = AmmoPickup;
    						AmmoPickup.ReplacementFactory = NewAmmo;
    						return false;
    					}
    				}
    			}
    		}
    	}
    
    	return true;
    }
    
    defaultproperties
    {
       WeaponsToReplace(0)=(OldClassName="UTWeap_FlakCannon",NewClassPath="Ripper.UTWeap_Ripper")
       AmmoToReplace(0)=(OldClassName="UTAmmo_FlakCannon",NewClassPath="Ripper.UTAmmo_Ripper")
       GroupNames(0)="RIPPERMOD"
       Components(0)=Sprite
       Name="Default__UTMutator_Ripper"
       ObjectArchetype=UTMutator'UTGame.Default__UTMutator'
    }

    #2
    I have already changed or removed bits and pieces in the code like I changed in default properties I changed:

    Code:
     defaultproperties
    {
       WeaponsToReplace(0)=(OldClassName="UTWeap_FlakCannon",NewClassPath="Ripper.UTWeap_Ripper")
       AmmoToReplace(0)=(OldClassName="UTAmmo_FlakCannon",NewClassPath="Ripper.UTAmmo_Ripper")
       GroupNames(0)="RIPPERMOD"
       Components(0)=Sprite
       Name="Default__UTMutator_Ripper"
       ObjectArchetype=UTMutator'UTGame.Default__UTMutator'
    }
    to:

    Code:
    defaultproperties
    {
       WeaponsToAdd(0)=(NewClassPath="Ripper.UTWeap_Ripper")
       AmmoToAdd(0)=(NewClassPath="Ripper.UTAmmo_Ripper")
       GroupNames(0)="RIPPERMOD"
       Begin Object Class=SpriteComponent Name=Sprite ObjName=Sprite Archetype=SpriteComponent'UTGame.Default__UTMutator:Sprite'
          ObjectArchetype=SpriteComponent'UTGame.Default__UTMutator:Sprite'
       End Object
       Components(0)=Sprite
       Name="Default__UTMutator_Ripper"
       ObjectArchetype=UTMutator'UTGame.Default__UTMutator'
    }
    I also deleted the two arrays WeaponsToReplace and AmmoToReplace plus the struct ReplacementInfo function.

    Comment


      #3
      I haven't checked the code into detail but where are you stuck? Most of the code (probably about 99%) comes from the original Weapon Replacement mutator.

      Note:
      I'm pretty sure I shared several weapon replacement mutator snippets on this forum. Let me check that, otherwise I'm gonna post a ready-to-use snippet, again.

      Edit:
      I didn't found the exact code snippet I was thinking of but here is one:
      Originally posted by RattleSN4K3 View Post
      Code:
      // Based on UTMutator_WeaponReplacement
      class PunisherReplacementMutator extends UTMutator;
      
      var class<UTWeapon>  WeaponClassOld;
      var class<UTWeapon>  WeaponClassNew;
      
      var class<UTAmmoPickupFactory>  AmmoClassOld;
      var class<UTAmmoPickupFactory>  AmmoClassNew;
      
      function PostBeginPlay()
      {
          local UTGame Game;
          local int i;
      
          Super.PostBeginPlay();
      
          // replace default weapons
          Game = UTGame(WorldInfo.Game);
          if (Game != None)
          {
              for (i = 0; i < Game.DefaultInventory.length; i++)
              {
                  if (Game.DefaultInventory[i] != None && ClassIsChildOf(Game.DefaultInventory[i], WeaponClassOld))
                  {
                      if (WeaponClassNew == none)
                      {
                          // replace with nothing
                          Game.DefaultInventory.Remove(i, 1);
                          i--;
                      }
                      Game.DefaultInventory[i] = WeaponClassNew;
                  }
              }
          }
      }
      
      function bool CheckReplacement(Actor Other)
      {
          local UTWeaponPickupFactory WeaponPickup;
          local UTWeaponLocker Locker;
          local UTAmmoPickupFactory AmmoPickup, NewAmmo;
          local int i;
      
          WeaponPickup = UTWeaponPickupFactory(Other);
          if (WeaponPickup != None)
          {
              if (WeaponPickup.WeaponPickupClass != None && ClassIsChildOf(WeaponPickup.WeaponPickupClass, WeaponClassOld))
              {
                  if (WeaponClassNew == none)
                  {
                      // replace with nothing
                      return false;
                  }
                  WeaponPickup.WeaponPickupClass = WeaponClassNew;
                  WeaponPickup.InitializePickup();
              }
          }
          else
          {
              Locker = UTWeaponLocker(Other);
              if (Locker != None)
              {
                  for (i = 0; i < Locker.Weapons.length; i++)
                  {
                      if (Locker.Weapons[i].WeaponClass != None && ClassIsChildOf(Locker.Weapons[i].WeaponClass, WeaponClassOld))
                      {
                          if (WeaponClassNew == none)
                          {
                              // replace with nothing
                              Locker.ReplaceWeapon(i, None);
                          }
                          else
                          {
                              Locker.ReplaceWeapon(i, WeaponClassNew);
                          }
                      }
                  }
              }
              else
              {
                  AmmoPickup = UTAmmoPickupFactory(Other);
                  if (AmmoPickup != None && ClassIsChildOf(AmmoPickup.Class, AmmoClassOld))
                  {
                      if (AmmoClassNew == None)
                      {
                          // replace with nothing
                          return false;
                      }
                      else if (AmmoClassNew.default.bStatic || AmmoClassNew.default.bNoDelete)
                      {
                          // transform the current ammo into the desired class
                          AmmoPickup.TransformAmmoType(AmmoClassNew);
                          return true;
                      }
                      else
                      {
                          // spawn the new ammo, link it to the old, then disable the old one
                          NewAmmo = AmmoPickup.Spawn(AmmoClassNew);
                          NewAmmo.OriginalFactory = AmmoPickup;
                          AmmoPickup.ReplacementFactory = NewAmmo;
                          return false;
                      }
                  }
              }
          }
      
          return true;
      }
      
      defaultproperties
      {
          WeaponClassOld=class'UTWeap_Enforcer'
          WeaponClassNew=class'MyWeap_Punisher'
      
          AmmoClassOld=class'UTAmmo_Enforcer'
          AmmoClassNew=class'MyAmmo_Punisher'
      }
      You can also check that thread as there are several issues with weapon replacement mentioned.

      ------------------

      Additionally, I've recently prototyped a replacement mutator for UT4 with UT3 (called something like Central Weapon Replacement). During the development, I've created several replacement mutators. A basic one can be found here:
      https://forums.unrealtournament.com/...l=1#post159163

      Several other example mutators can be found in the specific repo here:
      https://github.com/RattleSN4K3/UT4Pr...ponReplacement


      Feel free to ask, if you need further help regarding my or your code.

      Comment


        #4
        The code came from the Ripper weapon mod (Ripper heavy not lite). I actually downloaded it from a link in a thread about the riiper at this forum I am pretty sure. I installed it then loaded the u. file in the editor and exported all scripts. I ended up with the UTMutator_Ripper.uc file along with the associated weapon uc files such as UTWeap_Ripper and UTAmmo_Ripper. I didn't change these other files cause I am happy with the actual gun itself and I tested it in the editor to see if I could place it in weapon pickup factories and I could once I loaded the u. file. Where I am stuck is this section:

        Code:
        function PostBeginPlay()
        {
        	local UTGame Game;
        	local int i, Index;
        
        	Super.PostBeginPlay();
        
        	// replace default weapons
        	Game = UTGame(WorldInfo.Game);
        	if (Game != None)
        	{
        		for (i = 0; i < Game.DefaultInventory.length; i++)
        		{
        			if (Game.DefaultInventory[i] != None)
        			{
        				Index = WeaponsToReplace.Find('OldClassName', Game.DefaultInventory[i].Name);
        				if (Index != INDEX_NONE)
        				{
        					if (WeaponsToReplace[Index].NewClassPath == "")
        					{
        						// replace with nothing
        						Game.DefaultInventory.Remove(i, 1);
        						i--;
        					}
        					Game.DefaultInventory[i] = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
        				}
        			}
        		}
        
        		if (Game.TranslocatorClass != None)
        		{
        			Index = WeaponsToReplace.Find('OldClassName', Game.TranslocatorClass.Name);
        			if (Index != INDEX_NONE)
        			{
        				if (WeaponsToReplace[Index].NewClassPath == "")
        				{
        					// replace with nothing
        					Game.TranslocatorClass = None;
        				}
        				else
        				{
        					Game.TranslocatorClass = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
        				}
        			}
        		}
        	}
        }
        
        function bool CheckReplacement(Actor Other)
        {
        	local UTWeaponPickupFactory WeaponPickup;
        	local UTWeaponLocker Locker;
        	local UTAmmoPickupFactory AmmoPickup, NewAmmo;
        	local int i, Index;
        	local class<UTAmmoPickupFactory> NewAmmoClass;
        
        	WeaponPickup = UTWeaponPickupFactory(Other);
        	if (WeaponPickup != None)
        	{
        		if (WeaponPickup.WeaponPickupClass != None)
        		{
        			Index = WeaponsToReplace.Find('OldClassName', WeaponPickup.WeaponPickupClass.Name);
        			if (Index != INDEX_NONE)
        			{
        				if (WeaponsToReplace[Index].NewClassPath == "")
        				{
        					// replace with nothing
        					return false;
        				}
        				WeaponPickup.WeaponPickupClass = class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class'));
        				WeaponPickup.InitializePickup();
        			}
        		}
        	}
        	else
        	{
        		Locker = UTWeaponLocker(Other);
        		if (Locker != None)
        		{
        			for (i = 0; i < Locker.Weapons.length; i++)
        			{
        				if (Locker.Weapons[i].WeaponClass != None)
        				{
        					Index = WeaponsToReplace.Find('OldClassName', Locker.Weapons[i].WeaponClass.Name);
        					if (Index != INDEX_NONE)
        					{
        						if (WeaponsToReplace[Index].NewClassPath == "")
        						{
        							// replace with nothing
        							Locker.ReplaceWeapon(i, None);
        						}
        						else
        						{
        							Locker.ReplaceWeapon(i, class<UTWeapon>(DynamicLoadObject(WeaponsToReplace[Index].NewClassPath, class'Class')));
        						}
        					}
        				}
        			}
        		}
        		else
        		{
        			AmmoPickup = UTAmmoPickupFactory(Other);
        			if (AmmoPickup != None)
        			{
        				Index = AmmoToReplace.Find('OldClassName', AmmoPickup.Class.Name);
        				if (Index != INDEX_NONE)
        				{
        					if (AmmoToReplace[Index].NewClassPath == "")
        					{
        						// replace with nothing
        						return false;
        					}
        					NewAmmoClass = class<UTAmmoPickupFactory>(DynamicLoadObject(AmmoToReplace[Index].NewClassPath, class'Class'));
        					if (NewAmmoClass == None)
        					{
        						// replace with nothing
        						return false;
        					}
        					else if (NewAmmoClass.default.bStatic || NewAmmoClass.default.bNoDelete)
        					{
        						// transform the current ammo into the desired class
        						AmmoPickup.TransformAmmoType(NewAmmoClass);
        						return true;
        					}
        					else
        					{
        						// spawn the new ammo, link it to the old, then disable the old one
        						NewAmmo = AmmoPickup.Spawn(NewAmmoClass);
        						NewAmmo.OriginalFactory = AmmoPickup;
        						AmmoPickup.ReplacementFactory = NewAmmo;
        						return false;
        					}
        				}
        			}
        		}
        	}
        
        	return true;
        }
        I am going to try and delete some of it tomorrow. Where I am stuck is I am not sure if I need to add anything. Just mentioning again I don't want to replace I want to add.

        Comment


          #5
          Does the edit Post button not work at the moment in these forums?

          Comment


            #6
            So, you want to manually add a custom weapon to an existing mapper within the editor (and not programmitcally)?
            In general, you have to load the package (and all the related files) and select your weapon in the weapon drop down. Nothing else. You don't need any source to include a weapon. Cooking is a tricky part though.


            Originally posted by UTCollector88 View Post
            Does the edit Post button not work at the moment in these forums?
            Nope. There are several other bugs. I reported them here:
            https://forums.unrealtournament.com/...478#post181478

            You can edit your post by opening that link in a new tab/window.

            Comment


              #7
              I want to do it programmatically as well. I was just seeing it worked in the editor as well just in case I decide to make a new map with that weapon.

              Comment


                #8
                So, you want to add your custom factory (MyFactory_Ripper) to an existing map programmatically? Well basically, you first need to set 2 important flags to spawn a factory on runtime. bStatic and bNoDelete both to false. In this case, you can spawn your factory with the WorldInfo.Spawn(...) method.

                If you know the location where you want to spawn your factories to, you only have to create something like this:
                Code:
                class MyMutator extends UTMutator;
                
                var array<Vector> WeaponLocations;
                var class<Actor> WeaponFactoryClass;
                
                function PostBeginPlay()
                {
                    local int i;
                    super.PostBeginPlay();
                
                    for (i=0; i<WeaponLocations.Length; i++)
                    {
                        Spawn(WeaponFactoryClass,,, WeaponLocations[i]);
                    }
                }
                
                defaultproperties
                {
                    WeaponFactoryClass=class'MyFactory_Ripper'
                }
                with your factory being something liks this:

                Code:
                class MyFactory_Ripper extends UTWeaponPickupFactory;
                
                defaultproperties
                {
                    // allow dynamic spawn
                    bStatic=false
                    bNoDelete=false
                
                    WeaponPickupClass=class'MyRipper'
                }
                Note:
                You cannot spawn UTWeaponPickupFactory dynamically on runtime as the original class has still bNoDelete set. Without ugly hacks (and not fully reliable), you cannot spawn this class anywhere anywhen. Creating a custom factory for this use case is the proper way. Other than that, you can actually find ressources on this forum to spawn such class on runtime. I've posted it somewhere in response to NickG. Could not find it atm.

                PS: Still not sure what you exactly want to do (completely).

                Comment


                  #9
                  [QUOTE=RattleSN4K3;31931286]So, you want to add your custom factory (MyFactory_Ripper) to an existing map programmatically? Well basically, you first need to set 2 important flags to spawn a factory on runtime. bStatic and bNoDelete both to false. In this case, you can spawn your factory with the WorldInfo.Spawn(...) method.

                  If you know the location where you want to spawn your factories to, you only have to create something like this:
                  Code:
                  class MyMutator extends UTMutator;
                  
                  var array<Vector> WeaponLocations;
                  var class<Actor> WeaponFactoryClass;
                  
                  function PostBeginPlay()
                  {
                      local int i;
                      super.PostBeginPlay();
                  
                      for (i=0; i<WeaponLocations.Length; i++)
                      {
                          Spawn(WeaponFactoryClass,,, WeaponLocations[i]);
                      }
                  }
                  
                  defaultproperties
                  {
                      WeaponFactoryClass=class'MyFactory_Ripper'
                  }
                  with your factory being something liks this:

                  Code:
                  class MyFactory_Ripper extends UTWeaponPickupFactory;
                  
                  defaultproperties
                  {
                      // allow dynamic spawn
                      bStatic=false
                      bNoDelete=false
                  
                      WeaponPickupClass=class'MyRipper'
                  }
                  Yes that is exactly what the type of thing I want to do. What I am confused about is the location part of it.

                  Comment


                    #10
                    Originally posted by RattleSN4K3 View Post

                    Note:
                    You cannot spawn UTWeaponPickupFactory dynamically on runtime.
                    What do you mean spawn dynamically on runtime?

                    Comment


                      #11
                      Originally posted by UTCollector88 View Post
                      Originally posted by RattleSN4K3 View Post
                      CODE
                      Yes that is exactly what the type of thing I want to do. What I am confused about is the location part of it.
                      I left any configuration of locations out of the code as I'm not sure how you think of adding custom weapons to existing maps.


                      Originally posted by UTCollector88 View Post
                      What do you mean spawn dynamically on runtime?
                      With the stock/core/original content, you have to use UTWeaponPickupFactory for adding any new weapon at any location on a map. That class can only be placed in the editor into maps. You cannot spawn this class by a mutator/gametype (when somebody is using such mutator). Mutators which are adding some kind of new weapon factories (at random different location) are (most likely) using a subclass of that class with modified flags of bNoDelete and bStatic. Either one of these flags (if set to true) will prevent spawning such actor by using Spawn(...).

                      So in case you "want to do it programmatically as well", you have to create some sort of custom weapon factory where you set up the specific weapon (which can be done dynamically programmatically; but also statically on designtime... via defaultproperties).


                      In case you are only trying to replace a given weapon in a map with your custom weapon (Ripper for instance), you can just use CheckReplacement etc. to replace the weapon of the weapon factory on runtime. Within the editor, you can only change the weapon factories' weapon and save the map under a different map (which user then have to download as well). This is how most of the existing weapon mods work (if they are shipped with a mutator).


                      The previous code would feature adding custom factory (with a custom weapon) to a map by spawning that factory at given locations. The tricky part is the location determination. Every map has a different layout, locations for a map of Deck is different to locations for Defiance for instance. You should/could/have-to use/store such locations per map. Another approach would be using existing Navigation points, but these are also not commonly named/positioned accross all maps.

                      A good example would be Wormo's ServerDecals mutator for UT2004:
                      https://forums.epicgames.com/threads...-Server-Decals

                      Another example (where my personal experience comes from) is an Adrenaline mutator for UT3 which I (still; from time to time) develop where the Arenaline pickups are added dynamically (on runtime) to maps without the need of modified maps.


                      My queston still remains the same , in order to help you on that case specifically:
                      What do you want to achieve exactly? The ripper mutator code does not feature any sort of code which would add a new factory to a map. There is no need to use that class for reference.

                      Comment


                        #12
                        Originally posted by RattleSN4K3 View Post
                        So in case you "want to do it programmatically as well", you have to create some sort of custom weapon factory where you set up the specific weapon (which can be done dynamically programmatically; but also statically on designtime... via defaultproperties).

                        The previous code would feature adding custom factory (with a custom weapon) to a map by spawning that factory at given locations. The tricky part is the location determination. Every map has a different layout, locations for a map of Deck is different to locations for Defiance for instance. You should/could/have-to use/store such locations per map. Another approach would be using existing Navigation points, but these are also not commonly named/positioned accross all maps.
                        That is the sort of think I wanted to do. I think the ballistic weapon mod works like this as well. Are you familiar with that mod? Its has got about 20 weapons I think. It replaces the existing weapon factories and adds new weapons factories I think.

                        Comment


                          #13
                          Still not sure what you want to do.
                          • Adding weapons to existing maps where any other pickup isn't located
                          • Adding weapon existing weapon factories (which is replacing)
                          • Exchanging the existing weapon factories with a custom one (which isn't required if the factory doesn't do any fancy stuff)


                          Not sure what the Ballistic weapon mod does, but probably it is just replacing the type of weapons with a ballistic one. Not sure if the pickup bases are customized with that mod.

                          Comment


                            #14
                            Originally posted by RattleSN4K3 View Post
                            Still not sure what you want to do.
                            • Adding weapons to existing maps where any other pickup isn't located
                            • Adding weapon existing weapon factories (which is replacing)
                            • Exchanging the existing weapon factories with a custom one (which isn't required if the factory doesn't do any fancy stuff)

                            The first point is the one I wanted to do.

                            Comment


                              #15
                              Quick update. I'm gonna share something with/for you. Probably tomorrow. It will be an example mutator with everything you need. Simplistic but working. Easy to use. Editor mode. Multiple map support. Custom and stock support. Easy to extend. Also something you probably didn't see that often.

                              Comment

                              Working...
                              X