Modding:Mutations: Difference between revisions

From Caves of Qud Wiki
Jump to navigation Jump to search
mNo edit summary
mNo edit summary
Line 460: Line 460:
</syntaxhighlight>
</syntaxhighlight>


== Exclusions ==
=== Exclusions ===
The Exclusions parameter defines mutations that should be considered mutually exclusive with this mutation. For example, you can only have one type of back-slot mutation, so the game defines the other three types of back-slot mutations as Exclusions in the Mutations.xml file.
The Exclusions parameter defines mutations that should be considered mutually exclusive with this mutation. For example, you can only have one type of back-slot mutation, so the game defines the other three types of back-slot mutations as Exclusions in the Mutations.xml file.


Line 468: Line 468:
</syntaxhighlight>
</syntaxhighlight>


== Code ==
=== Code ===


The Code element value is used when constructing a Build Library code for characters you create.
The Code element value is used when constructing a Build Library code for characters you create.

Revision as of 00:20, 26 June 2019

Tutorial

First, include a mutations.xml in your mod that defines a new mutation. Mutations.xml sample, adding a simple mod

<?xml version="1.0" encoding="utf-8" ?>
<mutations>
  <category Name="Physical">
    <mutation Name="Udder" Cost="1" MaxSelected="1" Class="FreeholdTutorial_Udder" Exclusions="" Code="ea"></mutation>
 </category>
</mutations>

Then add a new .cs file in your mod that implements the class. Here's a skeletal implementation of the entry above. It must be in the XRL.World.Parts.Mutation namespace and must ultimately descend from BaseMutation. (Though not necessarily directly, if you have a very complex mod)


Skeletal mutation example.

using System;
using System.Collections.Generic;
using System.Text;
 
using XRL.Rules;
using XRL.Messages;
using ConsoleLib.Console;
 
namespace XRL.World.Parts.Mutation
{
    [Serializable]
    class FreeholdTutorial_Udder : BaseMutation
    {
        public FreeholdTutorial_Udder()
        {
            Name = "FreeholdTutorial_Udder";
            DisplayName = "Udder";
        }
 
        public override void Register(GameObject Object)
        {
        }
         
        public override string GetDescription()
        {
            return "";
        }
 
        public override string GetLevelText(int Level)
        {
            string Ret = "You have udders.\n";
            return Ret;
        }
 
        public override bool BeforeRender(Event E)
        {
            if (ParentObject.IsPlayer())
            {
                if (ParentObject.pPhysics != null && ParentObject.pPhysics.CurrentCell != null)
                {
                    ParentObject.pPhysics.CurrentCell.ParentZone.AddLight(ParentObject.pPhysics.CurrentCell.X, ParentObject.pPhysics.CurrentCell.Y, Level, LightLevel.Darkvision);
                }
            }
            return true;
        }
 
        public override bool FireEvent(Event E)
        {
            return base.FireEvent(E);
        }
 
        public override bool ChangeLevel(int NewLevel)
        {
            return true;
        }
 
        public override bool Mutate(GameObject GO, int Level)
        {
            return true;
        }
 
        public override bool Unmutate(GameObject GO)
        {
            return true;
        }
    }
}


Here's a full example of the Flaming Hands mutation from the game's source code. Flaming Hands - Full Example .cs

using System;
using System.Collections.Generic;
using XRL.UI;
using ConsoleLib.Console;
 
namespace XRL.World.Parts.Mutation
{
    [Serializable]
    class FlamingHands : BaseMutation
    {
        public FlamingHands()
        {
            Name = "FlamingHands";
            DisplayName = "Flaming Hands";
        }
 
        public Guid FlamingHandsActivatedAbilityID = Guid.Empty;
        public ActivatedAbilityEntry FlamingHandsActivatedAbility = null;
 
        public override void Register(GameObject Object)
        {
            Object.RegisterPartEvent(this, "BeginEquip");
            Object.RegisterPartEvent(this, "CommandFlamingHands");
            Object.RegisterPartEvent(this, "AIGetOffensiveMutationList");
        }
 
        public override string GetDescription()
        {
            return "You emit jets of flame from your hands.";
        }
 
        public override string GetLevelText(int Level)
        {
            string Ret = "Emits a 9-square ray of flame in the direction of your choice\n";
            Ret += "Cooldown: 10 rounds\n";
            Ret += "Damage: " + Level + "d6\n";
            Ret += "Cannot wear gloves";
            return Ret;
        }
 
        public void Flame(Cell C, ScreenBuffer Buffer)
        {
            string Damage = Level + "d6";
 
            Body pBody = ParentObject.GetPart("Body") as Body;
            int nHandCount = pBody.GetPart("Hands").Count-1;
            if (nHandCount > 0) Damage += "+" + nHandCount.ToString();
 
            if (C != null)
            {
                List<GameObject> Objects = C.GetObjectsInCell();
 
                foreach (GameObject GO in Objects)
                {
                    if( GO.PhasedMatches( ParentObject ) )
                    {
                        GO.FireEvent(Event.New("TemperatureChange", "Amount", 310 + (30 * Level), "Owner", ParentObject));
                        for (int x = 0; x < 5; x++) GO.ParticleText("&r" + (char)(219 + Rules.Stat.Random(0, 4)), 2.9f, 1);
                        for (int x = 0; x < 5; x++) GO.ParticleText("&R" + (char)(219 + Rules.Stat.Random(0, 4)), 2.9f, 1);
                        for (int x = 0; x < 5; x++) GO.ParticleText("&W" + (char)(219 + Rules.Stat.Random(0, 4)), 2.9f, 1);
                    }
                }
 
                foreach (GameObject GO in C.GetObjectsWithPart("Combat"))
                {
                    if( GO.PhasedMatches( ParentObject ) )
                    {
                        Damage Dmg = new Damage(Rules.Stat.Roll(Damage));
                        Dmg.AddAttribute("Fire");
                        Dmg.AddAttribute("Heat");
 
                        Event eTakeDamage = Event.New("TakeDamage");
                        eTakeDamage.AddParameter("Damage", Dmg);
                        eTakeDamage.AddParameter("Owner", ParentObject);
                        eTakeDamage.AddParameter("Attacker", ParentObject);
                        eTakeDamage.AddParameter("Message", "from %o flames!");
 
                        GO.FireEvent(eTakeDamage);
                    }
                }
            }
 
            Buffer.Goto(C.X, C.Y);
            string sColor = "&C";
            int r = Rules.Stat.Random(1, 3);
            if (r == 1) sColor = "&R";
            if (r == 2) sColor = "&r";
            if (r == 3) sColor = "&W";
 
            r = Rules.Stat.Random(1, 3);
            if (r == 1) sColor += "^R";
            if (r == 2) sColor += "^r";
            if (r == 3) sColor += "^W";
 
            if( C.ParentZone == XRL.Core.XRLCore.Core.Game.ZoneManager.ActiveZone )
            {
                r = Rules.Stat.Random(1, 3);
                Buffer.Write(sColor + (char)(219 + Rules.Stat.Random(0, 4)));
                Popup._TextConsole.DrawBuffer(Buffer);
                System.Threading.Thread.Sleep(10);
            }
        }
 
        public override bool FireEvent(Event E)
        {
            if (E.ID == "AIGetOffensiveMutationList")
            {
                int Distance = (int)E.GetParameter("Distance");
                GameObject Target = E.GetParameter("Target") as GameObject;
                List<XRL.World.AI.GoalHandlers.AICommandList> CommandList = (List<XRL.World.AI.GoalHandlers.AICommandList>)E.GetParameter("List");
 
                if (FlamingHandsActivatedAbility != null && FlamingHandsActivatedAbility.Cooldown <= 0 && Distance <= 9 && ParentObject.HasLOSTo(Target) ) CommandList.Add(new XRL.World.AI.GoalHandlers.AICommandList("CommandFlamingHands", 1));
                return true;
            }
 
            if (E.ID == "CommandFlamingHands")
            {
                ScreenBuffer Buffer = new ScreenBuffer(80, 25);
                Core.XRLCore.Core.RenderMapToBuffer(Buffer);
 
                List<Cell> TargetCell = PickLine(9, AllowVis.Any);
                if (TargetCell == null) return true;
                if (TargetCell.Count <= 0) return true;
 
                if (TargetCell != null)
                {
                    if (TargetCell.Count == 1)
                    {
                        if (ParentObject.IsPlayer())
                            if (UI.Popup.ShowYesNoCancel("Are you sure you want to target yourself?") != DialogResult.Yes)
                            {
                                return true;
                            }
                    }
 
                    if( FlamingHandsActivatedAbility != null ) FlamingHandsActivatedAbility.Cooldown = 110;
                    ParentObject.FireEvent(Event.New("UseEnergy", "Amount", 1000, "Type", "Physical Mutation"));
                     
                    for (int x = 0; x < 9 && x < TargetCell.Count; x++)
                    {
                        if (TargetCell.Count == 1 || TargetCell[x] != ParentObject.pPhysics.CurrentCell)
                        Flame(TargetCell[x],Buffer);
 
                        foreach( GameObject GO in TargetCell[x].GetObjectsWithPart("Physics") )
                        {
                            if (GO.pPhysics.Solid)
                            {
                                x = 999;
                                break;
                            }
                        }
                    }
                }
            }
 
            if (E.ID == "BeginEquip")
            {
                GameObject Equipment = E.GetParameter("Object") as GameObject;
                string BodyPartName = E.GetParameter("BodyPartName") as string;
 
                if (BodyPartName == "Hands")
                {
                    if (IsPlayer())
                    {
                        UI.Popup.Show("Your flaming hands prevents you from equipping " + Equipment.DisplayName + "!");
                    }
 
                    E.bCancelled = true;
                    return false;
                }
            }
 
            return true;
        }
 
 
        int OldFlame = -1;
        int OldVapor = -1;
 
        public override bool ChangeLevel(int NewLevel)
        {
            Physics pPhysics = ParentObject.GetPart("Physics") as Physics;
 
            TemperatureOnHit pTemp = FlamesObject.GetPart("TemperatureOnHit") as TemperatureOnHit;
            pTemp.Amount =  (Level*2) + "d8";
 
            return base.ChangeLevel(NewLevel);
        }
 
        public override bool Mutate(GameObject GO, int Level)
        {
            Unmutate(GO);
 
            ActivatedAbilities pAA = GO.GetPart("ActivatedAbilities") as ActivatedAbilities;
            Physics pPhysics = GO.GetPart("Physics") as Physics;
 
            if (pPhysics != null)
            {
                OldFlame = pPhysics.FlameTemperature;
                OldVapor = pPhysics.VaporTemperature;
            }
             
            Body pBody = GO.GetPart("Body") as Body;
            if (pBody != null)
            {
                GO.FireEvent(Event.New("CommandForceUnequipObject", "BodyPartName", "Hands"));
                FlamesObject = GameObjectFactory.Factory.CreateObject("Ghostly Flames");
                Event eCommandEquipObject = Event.New("CommandEquipObject");
                eCommandEquipObject.AddParameter("Object", FlamesObject);
                eCommandEquipObject.AddParameter("BodyPartName", "Hands");
                GO.FireEvent(eCommandEquipObject);
            }
 
            FlamingHandsActivatedAbilityID = pAA.AddAbility("Flaming Hands", "CommandFlamingHands", "Physical Mutation");
            FlamingHandsActivatedAbility = pAA.AbilityByGuid[FlamingHandsActivatedAbilityID];
            return true;
        }
 
        public GameObject FlamesObject = null;
 
        public override bool Unmutate(GameObject GO)
        {
            Physics pPhysics = GO.GetPart("Physics") as Physics;
 
            if (pPhysics != null)
            {
                if (OldFlame != -1) pPhysics.FlameTemperature = OldFlame;
                if (OldVapor != -1) pPhysics.BrittleTemperature = OldVapor;
                OldFlame = -1;
                OldVapor = -1;
 
                pPhysics.Temperature = 25;
            }
             
            Body pBody = GO.GetPart("Body") as Body;
            if (pBody != null)
            {
                BodyPart pMainBody = pBody.GetPartByName("Hands");
                if( pMainBody != null )
                if (pMainBody.Equipped != null)
                {
                    if (pMainBody.Equipped.Blueprint == "Ghostly Flames")
                    {
                        pMainBody.Equipped.FireEvent(Event.New("Unequipped", "UnequippingObject", ParentObject, "BodyPart", pMainBody));
                        pMainBody.Unequip();
                    }
                }
            }
 
            if (FlamingHandsActivatedAbilityID != Guid.Empty)
            {
                ActivatedAbilities pAA = GO.GetPart("ActivatedAbilities") as ActivatedAbilities;
                pAA.RemoveAbility(FlamingHandsActivatedAbilityID);
                FlamingHandsActivatedAbilityID = Guid.Empty;
            }
 
            return true;
        }
    }
}

Your namespace should be XRL.World.Parts.Mutations, the class should be marked serializeable and derived from BaseMutation.

using System;
using System.Collections.Generic;
using XRL.UI;
using ConsoleLib.Console;
 
namespace XRL.World.Parts.Mutation
{
    [Serializable]
    class FlamingHands : BaseMutation
    {


GetDescription and GetLevelText are called to generate the descriptive for a given level of the mutation.

        public override string GetDescription()
        {
            return "You emit jets of flame from your hands.";
        }
  
        public override string GetLevelText(int Level)
        {
            string Ret = "Emits a 9-square ray of flame in the direction of your choice\n";
            Ret += "Cooldown: 10 rounds\n";
            Ret += "Damage: " + Level + "d6\n";
            Ret += "Cannot wear gloves";
            return Ret;
        }


Change level is called any time the mutation changes level.

public override bool ChangeLevel(int NewLevel)
{
    Physics pPhysics = ParentObject.GetPart("Physics") as Physics;
 
    TemperatureOnHit pTemp = FlamesObject.GetPart("TemperatureOnHit") as TemperatureOnHit;
    pTemp.Amount =  (Level*2) + "d8";
 
    return base.ChangeLevel(NewLevel);
}


Mutate and Unmutate are called on an object when it gains or loses the mutation.

public override bool Mutate(GameObject GO, int Level)
  
public override bool Unmutate(GameObject GO)

BaseMutation derives from Part, so the typical event registration and handling functions are available

public override void Register(GameObject Object)
 
public override bool FireEvent(Event E)

Additional Details

You may add the following elements to a <mutation> tag in Mutations.xml

  • Constructor (optional)
  • Exclusions (optional)
  • Code

Constructor

This element should be a string argument (or a comma-delimited string of arguments, if there are more than one) to pass to the mutation's class constructor. All such arguments are received as string parameters in the mutation class constructor. For example, Corrosive Gas Generation and Sleep Gas Generation both use the same mutation class, GasGeneration. However, Mutations.xml passes a different argument to the constructor, which indicates which type of gas should be used.

Mutations.xml

<mutation Name="Sleep Gas Generation" Cost="2" MaxSelected="1" Class="GasGeneration" Constructor="SleepGas" Exclusions="Corrosive Gas Generation" BearerDescription="those whe expel sleep gass" Code="bu"></mutation>

GasGeneration.cs

namespace XRL.World.Parts.Mutation
{
	[Serializable]
	public class GasGeneration : BaseMutation
	{
		public GasGeneration(string _GasObject)
		{
			this.GasObject = _GasObject;
			this.SyncFromBlueprint();
		}

The GasObject property is set to to "SleepGas" in this case, because that was the value provided in the Constructor element of Mutations.xml.

Theoretically you could create a new mutation that generates any type of gas simply by adding a single <mutation> tag to a Mutations.xml file. For example, this Mutations.xml file alone would create a new mutation called "Confusion Gas Generation"

<?xml version="1.0" encoding="utf-8" ?>
<mutations>
  <category Name="Physical">
    <mutation Name="Confusion Gas Generation" Cost="2" MaxSelected="1" Class="GasGeneration" Constructor="ConfusionGas" Exclusions="" BearerDescription="those who expel confusion gas" Code="zz"></mutation>
  </category>
</mutations>

Exclusions

The Exclusions parameter defines mutations that should be considered mutually exclusive with this mutation. For example, you can only have one type of back-slot mutation, so the game defines the other three types of back-slot mutations as Exclusions in the Mutations.xml file.

Example from Mutations.xml:

<mutation Name="Stinger (Confusing Venom)" Cost="3" MaxSelected="1" Class="Stinger" Constructor="Confuse" Exclusions="Stinger (Paralyzing Venom),Stinger (Poisoning Venom),Wings" BearerDescription="those with stingers tipped with confusing venom" Code="bx"></mutation>

Code

The Code element value is used when constructing a Build Library code for characters you create.