User:MomBun/Sandbox

From Caves of Qud Wiki
< User:MomBun
Revision as of 01:11, 3 August 2022 by MomBun (talk | contribs)
Jump to navigation Jump to search
This page is about modding. See the modding overview for an abstract on modding.
This page is about modding. See the modding overview for an abstract on modding.
For best results, it's recommended to have read the following topics before this one:

XML, Parameters, and First Steps

One of the first and important steps in adding a mutation to your mod is the file Mutations.XML, in which you'll have to add this:

<?xml version="1.0" encoding="utf-8" ?>
<mutations>
  <category Name="[CATEGORY]">
    <mutation Name="[NAME]" Cost="[NUMBER]" MaxSelected="[NUMBER]" Class="[C# ID]" Tile="Mutations/"YOUR_IMAGE_HERE"></mutation>
 </category>
</mutations>

For an in-game example of what to input, here is the XML code for Flaming Hands from Mutations.XML

  <category Name="Physical" DisplayName="{{G|Physical Mutations}}" Property="PhysicalMutationShift" ForceProperty="PhysicalMutationForceShift" IncludeInMutatePool="true">
    <mutation Name="Flaming Ray" Cost="4" MaxSelected="1" Class="FlamingHands" Exclusions="Freezing Ray" BearerDescription="the flaming-handed" Code="bh" Tile="Mutations/flaming_ray.bmp" />
  </category>

Name

This is the name of the mutation as it will appear in game.

Cost

The mutation costs this many mutation points when a character selects it during the character creation process.

MaxSelected

The maximum number of copies of this mutation that can be selected during character creation.

Currently this is only used for Unstable Mutation and there is some hard-coded special handling of that mutation when it comes to details such as constructing a Build Library code for builds that include Unstable Mutation. It is not clear if this value can be set to more than 1 for a modded mutation without causing some problems.

Class

The name of the .cs Class object that is used to instantiate your mutation object.

MaxSelected

The maximum number of copies of this mutation that can be selected during character creation.

Tile

The tile you will be using for said mutation in-game.

By default most mutations use gold/yellow for detail and brown for foreground but this can be customized for any color of your choice, see Modding:Tiles and Modding:Colors_&_Object_Rendering for furtherr info on this.

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.

Here are an example of how this is constructed from both Mutations.XML and GasGeneration.cs

<mutation Name="Sleep Gas Generation" Cost="2" MaxSelected="1" Class="GasGeneration" Constructor="SleepGas" Exclusions="Corrosive Gas Generation" BearerDescription="those who expel sleep gas" Code="bu"></mutation>
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"

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.

BearerDescription

It appears that this description is used in some of the random generation algorithms for villages and history in the game. For example, if a village reveres mutants with the Multiple Arms mutation, they might use the string defined in Mutations.xml ("the many-armed") to describe them in their praises or monuments.

C# Scripting, and Making The Mutation

TO DO: ADD COMMENTS

This is the source code from FlamingHands.cs

using ConsoleLib.Console;
using System.Collections.Generic;
using System;
using XRL.UI;

namespace XRL.World.Parts.Mutation
{

    [Serializable]
    public class FlamingHands : BaseDefaultEquipmentMutation
    {

        public string BodyPartType = "Hands";
        public bool CreateObject = true;
        public string Sound = "Abilities/sfx_ability_mutation_flamingRay_attack";

        public Guid FlamingHandsActivatedAbilityID = Guid.Empty;

        [NonSerialized] private static GameObject _Projectile;
        private static GameObject Projectile
        {
            get
            {
                if (!GameObject.validate(ref _Projectile))
                {
                    _Projectile = GameObject.createUnmodified("ProjectileFlamingHands");
                }
                return _Projectile;
            }
        }

        public override bool GeneratesEquipment()
        {
            return true;
        }

        public FlamingHands()
        {
            DisplayName = "Flaming Ray";
        }

        public override void Register(GameObject Object)
        {
            Object.RegisterPartEvent(this, "AIGetOffensiveMutationList");
            Object.RegisterPartEvent(this, "AttackerHit");
            Object.RegisterPartEvent(this, "CommandFlamingHands");
            base.Register(Object);
        }

        public override string GetCreateCharacterDisplayName()
        {
            return DisplayName + " (" + BodyPartType + ")";
        }

        public override string GetDescription()
        {
            BodyPart part = GetRegisteredSlot(BodyPartType, true);
            if (part != null)
            {
                return "You emit a ray of flame from your " + part.GetOrdinalName() + ".";
            }
            else
            {
                return "You emit a ray of flame.";
            }
        }

        public override string GetLevelText(int Level)
        {
            string Ret = "Emits a 9-square ray of flame in the direction of your choice.\n";            
            Ret += "Damage: {{rules|" + ComputeDamage(Level) + "}}\n";
            Ret += "Cooldown: 10 rounds\n";
            Ret += "Melee attacks heat opponents by {{rules|" + GetHeatOnHitAmount(Level) + "}} degrees";
            return Ret;
        }

        public string GetHeatOnHitAmount(int Level)
        {
            return (Level * 2) + "d8";
        }

        public string ComputeDamage(int UseLevel)
        {
            string Result = UseLevel + "d4";
            if (ParentObject != null)
            {
                int LimbCount = ParentObject.Body.GetPartCount(BodyPartType);
                if (LimbCount > 0)
                {
                    Result += "+" + LimbCount;
                }
            }
            else
            {
                Result += "+1";
            }
            return Result;
        }

        public string ComputeDamage()
        {
            return ComputeDamage(Level);
        }

        public void Flame(Cell C, ScreenBuffer Buffer, bool doEffect = true)
        {
            string Damage = ComputeDamage();

            if (C != null)
            {
                List<GameObject> Objects = C.GetObjectsInCell();

                foreach (GameObject GO in Objects)
                {
                    if( GO.PhaseMatches( ParentObject ) )
                    {
                        GO.TemperatureChange(310 + (25 * Level), Actor: ParentObject);
                        if( doEffect )
                        {
                            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);
                        }
                    }
                }

                int phase = ParentObject.GetPhase();
                Rules.DieRoll dmgRoll = Damage.GetCachedDieRoll();
                foreach (GameObject GO in C.GetObjectsWithPartReadonly("Combat"))
                {
                    GO.TakeDamage(
                        Amount: dmgRoll.Resolve(),
                        Attributes: "Fire",
                        Owner: ParentObject,
                        Message: "from %o flames!",
                        Phase: phase
                    );
                }
            }

            if( doEffect )
            {
                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 static bool Cast(FlamingHands mutation = null, string level = "5-6")
        {
            if (mutation == null)
            {
                mutation = new FlamingHands();
                mutation.Level = Rules.Stat.Roll(level);
                mutation.ParentObject = XRL.Core.XRLCore.Core.Game.Player.Body;
            }

            ScreenBuffer Buffer = ScreenBuffer.GetScrapBuffer1(true);
            Core.XRLCore.Core.RenderMapToBuffer(Buffer);

            List<Cell> TargetCell = mutation.PickLine(9, AllowVis.Any, Snap: true, IgnoreLOS: true, Label:"Flaming Ray"); //TODO:TARGETLABEL
            if (TargetCell == null || TargetCell.Count <= 0)
            {
                return false;
            }

            if (TargetCell.Count == 1 && mutation.ParentObject.IsPlayer())
            {
                if (UI.Popup.ShowYesNoCancel("Are you sure you want to target " + mutation.ParentObject.itself + "?") != DialogResult.Yes)
                {
                    return false;
                }
            }
            mutation.CooldownMyActivatedAbility(mutation.FlamingHandsActivatedAbilityID, Turns: 10);
            mutation.UseEnergy(1000, "Physical Mutation Flaming Hands");
            mutation.PlayWorldSound(mutation.Sound, combat: true);
            for (int i = 0, j = Math.Min(TargetCell.Count, 10); i < j; i++)
            {
                if (TargetCell.Count == 1 || TargetCell[i] != mutation.ParentObject.CurrentCell)
                {
                    mutation.Flame(TargetCell[i], Buffer);
                }
                if (i < j - 1 && TargetCell[i].IsSolidFor(Projectile: Projectile, Attacker: mutation.ParentObject))
                {
                    break;
                }
            }
            
            BodyPart part = mutation.GetRegisteredSlot(mutation.BodyPartType, false);
            XDidY(mutation.ParentObject, "emit", extra: "a flaming ray" + (part != null ? " from " + mutation.ParentObject.its + " " + part.GetOrdinalName() : ""), terminalPunctuation: "!", ColorAsGoodFor: mutation.ParentObject);
            return true;
        }

        public bool CheckObjectProperlyEquipped()
        {
            if( !CreateObject) return true;
            return HasRegisteredSlot(BodyPartType) && GetRegisteredSlot(BodyPartType,false) != null;
        }

        public override bool FireEvent(Event E)
        {
            if (E.ID == "AttackerHit")
            {
                if (!CheckObjectProperlyEquipped())
                {
                    return true;
                }

                GameObject Defender = E.GetGameObjectParameter("Defender");

                if (Defender != null)
                {
                    string Amount = GetHeatOnHitAmount(Level);
                    int MaxTemp = 400;

                    if ( (Rules.Stat.RollMax(Amount) > 0 && Defender.pPhysics.Temperature < MaxTemp) || (Rules.Stat.RollMax(Amount) < 0 && Defender.pPhysics.Temperature > MaxTemp))
                    {
                        Defender.TemperatureChange(Amount.RollCached(), Actor: E.GetGameObjectParameter("Attacker"), Phase: ParentObject.GetPhase());
                    }
                }
            }
            if (E.ID == "AIGetOffensiveMutationList")
            {
                if (
                    CheckObjectProperlyEquipped()
                    && E.GetIntParameter("Distance") <= 9
                    && IsMyActivatedAbilityAIUsable(FlamingHandsActivatedAbilityID)
                    && ParentObject.HasLOSTo(E.GetGameObjectParameter("Target"), UseTargetability: true)
                )
                {
                    E.AddAICommand("CommandFlamingHands");
                }
            }
            else
            if (E.ID == "CommandFlamingHands")
            {
                if (!CheckObjectProperlyEquipped())
                {
                    if (ParentObject.IsPlayer())
                    {
                        UI.Popup.ShowFail("Your " + BodyPartType + " is too damaged to do that!");
                    }
                    return false;
                }
                if (!Cast(this))
                {
                    return false;
                }
            }
            return base.FireEvent(E);
        }

        public override bool ChangeLevel(int NewLevel)
        {
            return base.ChangeLevel(NewLevel);
        }

        private void AddAbility()
        {
            FlamingHandsActivatedAbilityID = AddMyActivatedAbility(
                Name: "Flaming Ray",
                Command: "CommandFlamingHands",
                Class: "Physical Mutation",
                Icon: "" + (char) 168
            );
        }

        [NonSerialized]
        private static List<string> variants = new List<string> { "Hands", "Face", "Feet" };
        public override List<string> GetVariants()
        {
            return variants;
        }


        public override void SetVariant(int n)
        {
            if( n < variants.Count ) 
            {
                BodyPartType = variants[n];
            }
            else
            {
                BodyPartType = variants[0];
            }
            base.SetVariant(n);
        }

        public override void OnRegenerateDefaultEquipment(Body body)
        {
            base.OnRegenerateDefaultEquipment(body);
        }

        public void MakeFlaming( BodyPart part )
        {
            if( part == null ) return;

            if( part.DefaultBehavior != null && part.DefaultBehavior.Blueprint != "Ghostly Flames" && !part.DefaultBehavior.pRender.DisplayName.Contains("{{fiery|flaming}}"))
            {
                part.DefaultBehavior.pRender.DisplayName = "{{fiery|flaming}} " + part.DefaultBehavior.pRender.DisplayName;
            }

            if( part.Parts != null )
            {
                for (int x = 0; x < part.Parts.Count; x++)
                {
                    MakeFlaming(part.Parts[x]);
                }
            }
        }

        public override void OnDecorateDefaultEquipment(Body body)
        {
            if (CreateObject)
            {
                BodyPart part;
                if (!HasRegisteredSlot(BodyPartType))
                {
                    part = body.GetFirstPart(BodyPartType);
                    if (part != null) 
                    {
                        RegisterSlot(BodyPartType, part);
                    }
                    else
                    {
                        ;
                    }
                }
                else
                {
                    part = GetRegisteredSlot(BodyPartType, false);
                }

                if (part != null && part.DefaultBehavior == null)
                {
                    var FlamesObject = GameObject.create("Ghostly Flames");
                    FlamesObject.GetPart<Armor>().WornOn = BodyPartType;
                    part.DefaultBehavior = FlamesObject;
                }

                MakeFlaming(part);

                if (BodyPartType == "Hands")
                {
                    foreach (var hand in body.GetParts() )
                    {
                        if( hand.Type == "Hand") MakeFlaming(hand);
                    }
                }
            }

            base.OnDecorateDefaultEquipment(body);
        }

        public override bool Mutate(GameObject GO, int Level)
        {
            AddAbility();
            return base.Mutate(GO, Level);
        }

        public override bool Unmutate(GameObject GO)
        {
            RemoveMyActivatedAbility(ref FlamingHandsActivatedAbilityID);
            return base.Unmutate(GO);
        }

    }

}

TODO: ??? there has to be info about c# I'll need to add, probably expand upon my comments