[Tutorial] Creating Ticker-Based Events

Started by minami26, July 09, 2014, 04:58:55 AM

Previous topic - Next topic

minami26

Hi guys.
Today I'm sharing on How I made the Time or Ticker(RimWorld code Lingo) Based Events in my CustomEvents Mod

Today I'm sharing how I made one of my Favorite Event which is the

Mysterious Transmission Event

First you need to create the IncidentWorker Class so that you can Put it in the IncidentDefs XML.
The XML has the chance of this event happening we'll go into that later.

Here is the Commented Incident Worker Class.
//Rimworld
//Rimworld TechTreeMinami Mod by: minami26
//Rimworld

//This is a Tutorial for Ticker-Based Events.
//This IncidentWorker class is used to spawn a Ticker Building in the game.

//You define the chance of the event occurring in the incidentDefs XML.

//These are base references for code functions.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//These are references to RimWorld Code.
using UnityEngine;
using Verse;
using RimWorld;


namespace TTMCustomEvents
{
//You can change the class name
    public class IncidentWorker_MysteriousTransmission : IncidentWorker
    {
//This method is used to execute the incident.
        public override bool TryExecute(IncidentParms parms)
        {
//This function looks for a suitable cell in the map to spawn a ticker.
            IntVec3 intVec = GenCellFinder.RandomCellWith((IntVec3 sq) => sq.Standable() && !sq.IsFogged());

//This function creates the ticker building.
            Thing ticker = ThingMaker.MakeThing(ThingDef.Named("MTcounter"));

//This function spawns the ticker into the game.
            GenSpawn.Spawn(ticker, intVec);

//This determines if the incident has successfully initiated.
            return true;
        }

    }
}

//Rimworld
//Rimworld TechTreeMinami Mod by: minami26
//Rimworld


After you create the Incident Worker Class, we will then proceed to create the MTcounter Building that handles the Timeline of the Event.

The MTCounter building is basically the Ticker for this event.
For the duration of this event, The MTCounter building will provide the Tick() method to be used in the event.

Here is the Commented MTCounter building code.
//Rimworld
//Rimworld TechTreeMinami Mod by: minami26
//Rimworld

//This is a Tutorial for Ticker-Based Events.
//This IncidentWorker class is used to spawn a Ticker Building in the game.

//You define the chance of the event occurring in the incidentDefs XML.

//These are base references for code functions.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//You need these references to use RimWorld code functions.
using UnityEngine;
using Verse; //Verse contains most RimWorld method and Functions.
using Verse.AI; //Verse.AI deals with pawnAI specifics
using RimWorld; //RimWorld contains code interactions within the game.

//You can change the namespace to whatever you like.
namespace TTMCustomEvents
{

//You can change this class name to whatever you like.
    public class Ticker_MTcounter : Building //: Building : this references the type of Thing you are going to use. There are different kinds Building, ThingWithComponents, IncidentWorker etc.
    {

//These are instance variables.
        Pawn face, Mutator, abom; //Pawn class for dealing with specifying a pawn.
        int hours = UnityEngine.Random.Range(8000, 16000); //this variable contains the time amount and Randomizes the value.
//Time Table
//1 second IRL = 60Ticks
//1 gameHour = 2000Ticks
//1 gameDay = 20000Ticks

        bool justspawned = true, doonce = false, spawn = false; //these are determinants.

        IntVec3 place; //this variable will contain coordinates.

//SpawnSetup method is always used so that it will spawn the building.
        public override void SpawnSetup()
        {
            base.SpawnSetup();
            justspawned = true; //I'm using justspawned bool variable to instantiate starting phase of the event. when this becomes false it has finished starting process, then proceeds to second part for the Time Sequence of the Event.
        }

//The Ticker method makes everything move.
//This is called everytime the game moves.
        public override void Tick()
        {
            base.Tick();

            // This first part checks if the spacers have already spawned, if not then proceeds to create Spacers.

            //Log.Warning("");
            if (justspawned == true)
            {
//This line looks for pawns that incapacitated or Prisoners and if They are of Spacer Faction. then outputs it to the list variable.
                List<Pawn> list = (
                from Pawn g in Find.ListerPawns.AllPawns
                where g.Incapacitated || g.IsPrisonerOfColony && g.Faction.def.defName == "Spacer"
                select g).ToList<Pawn>();

                int c = list.Count(); //Counts how many pawns are in the list variable.

                if (c > 2)
                {
                  face = list.RandomElement<Pawn>(); //find a pawn to be set in the face variable.
                  justspawned = false;
                }
                else
                {
                    for (int i = 0; i < 2; i++)
                    {
//this line loops 2times to make 2 Spacers. If you set these variables outside the loop it will create
//The same pawn 2times, but only spawn one spacer.

//this determines the faction.
                        Faction faction = Find.FactionManager.FirstFactionOfDef(FactionDef.Named("Spacer"));

//this creates the pawn you want to be spawned.
                        Pawn pawn = PawnGenerator.GeneratePawn(PawnKindDef.Named("SpaceRefugee"), faction);

//looks for a suitable area.
                        IntVec3 land = GenCellFinder.RandomStandableClosewalkCellNear(base.Position, 5);

//this makes the spawned pawn incapacitated.
                        pawn.healthTracker.ForceIncap();

//this basically makes a pawn do something before it becomes psychotic.
                        pawn.jobs.StartJob(new Job(JobDefOf.Wait));

//This utility makes a psychotic, you can change the SanityState to any available type.
                        PsychologyUtility.TryDoMentalBreak(pawn, SanityState.Psychotic);

//this utility spawns a dropPod containing pawns.
                        DropPodUtility.MakeDropPodAt(land, new DropPodInfo
                        {
                            SingleContainedThing = pawn,
                            openDelay = 880,
                            leaveSlag = true
                        });
                    }

//These lines determines the face pawn variable.
//This is a single iteration of the above code.
                    Faction factionf = Find.FactionManager.FirstFactionOfDef(FactionDef.Named("Spacer"));
                    face = PawnGenerator.GeneratePawn(PawnKindDef.Named("SpaceRefugee"), factionf);

                    IntVec3 landf = GenCellFinder.RandomStandableClosewalkCellNear(base.Position, 5);
                    face.healthTracker.ForceIncap();
                    face.jobs.StartJob(new Job(JobDefOf.Wait));
                    PsychologyUtility.TryDoMentalBreak(face, SanityState.Psychotic);
                    DropPodUtility.MakeDropPodAt(landf, new DropPodInfo
                    {
                        SingleContainedThing = face,
                        openDelay = 880,
                        leaveSlag = true
                    });


//After the Spacers are spawned this will proceed to the Letter Notification of the Event.
                    justspawned = false;

//You set the message here.
//After 3 Updates to my mods you MUST set a string variable for your messages because RimWorld code changes a lot. Do not put the message in the method.
                    string text = "-------------Incoming Transmission-------------\n zzzbxzzx HELP!.... zxxcxbx We are under attack by an unknown lifeform!... zxcbcxzzz \n\n Please.. cvxzzxc HELP.xz..xz.. \nThe Ship is going dow.zxc.zx.x. We are escaping to a Rimworld.zx.c..... \n\n---------------End of Transmission--------------- \n\nDropPods Incoming!";

//This will make the Letter Notification appear in-game.
                    Find.History.AddGameEvent(text, GameEventType.BadNonUrgent, true, this.Position, string.Empty);
                    //----------------------^text variable is the message
//-----------------------------^GameEventType: There are 3 types BadNonUrgent = Yellow, BadUrgent = Red, Good = Blue.
//-------------------------------------------------------^Boolean to send Letter
//----------------------------------------------------------------^variable to determine the Go-To location of the event.
//--------------------------------------------------------------------------------^a DebugText **I haven't used this yet.
                }
            }

            // Check if already spawned
         
//Second Part
//The second part will start the time sequence of the events.
            hours--; //After instantiating the starting phase, the hours variable will then minus itself by 1, and do this every tick.
            if(face.IsInBed() && doonce == false) //Check if the face pawn is already in Bed. and makes it only do once.
            {
//Creates a message and sends a Letter Notification in-Game.
                string text = "The faces of the humans contained within the droppods looked terrified beyond belief, they must've experienced something horrifying.";
                Find.History.AddGameEvent(text, GameEventType.BadNonUrgent, true, face.Position, string.Empty);
                doonce = true;
            }

            if(hours <= 0) //When the hours variable is going below 0 or is 0 it will then proceed to the events you want to happen.
            {
//This is how I determine the chance. **Pretty lame right? :D
                int chance = UnityEngine.Random.Range(0, 100); //This gets a random value. min 0 max 100

                if (chance > 50) //A 50/50 chance to spawn the Abomination.
                {
//If chance is greater than 50 then.

//This gets a random pawn in the game that is a Human and Faction is Spacer.
                    if ((from pwn in Find.ListerPawns.AllPawns
                         where pwn.def.defName == "Human" && pwn.Faction.def.defName == "Spacer"
                         select pwn).TryRandomElement(out Mutator)) //Outputs the pawn into the variable name
                    {
//Create message and send a Letter Notification.
                        string text = "" + Mutator.Name.nick + " transformed into a huge abomination! that mysterious transmission dropped a Mutated Lifeform unto us! \n\nDestroy it before it slaughters all of the colonists!";
                        Find.History.AddGameEvent(text, GameEventType.BadUrgent, true, Mutator.Position, string.Empty);

                        place = Mutator.Position; //Save the position of the chosen pawn.

                        Mutator.health = 9999; //Makes the health of the Mutator pawn 9999
                        Explosion.DoExplosion(place, 0.1f, DamageTypeDefOf.Bomb, null);
//Creates an Explosion in the position, Explosion Radius, DmgType, Instigator

                        for (int b = 0; b < 90; b++)
                        {
//Creates blood and spews it all over the place.
                            IntVec3 SpewBlood = GenCellFinder.RandomStandableClosewalkCellNear(place, 4);
                            ThingDef blood = DefDatabase<ThingDef>.GetNamed("FilthBlood");
                            FilthMaker.MakeFilth(SpewBlood, blood, Mutator.Name.nick);
                        }

                        Mutator.Destroy(); //this removes the Mutator Pawn to replace it with the Abomination.

//Spawns the Abomination
                        Faction faction = Find.FactionManager.FirstFactionOfDef(FactionDef.Named("Abomination"));
                        abom = PawnGenerator.GeneratePawn(PawnKindDef.Named("Abomination"), faction);
                        GenSpawn.Spawn(abom, place);

//Makes the Abomination Psychotic
                        PsychologyUtility.TryDoMentalBreak(abom, SanityState.Psychotic);
                        spawn = true; //Bool Variable to determine if Abomination has Spawned
                    }
                }
                else {

//If chance is less than 50 then it will only send this message and nothing else.
                    string text = "It seems that the nightmare of the 3 Spacers has ended, Whatever attacked their spaceship may have been destroyed together with their ship. \n\nLet's just hope that they forget the terror they have experienced and continue to live on.";
                    Find.History.AddGameEvent(text, GameEventType.Good, true, string.Empty);

//This else condition is an Ending Phase of the Event
//You should then proceed to Destroy this building for Clean Up.
                    this.Destroy(); }             
            }

            if (spawn == true) //if abomination has spawned.
            {
                if (abom.healthTracker.Health < 100 || abom.health < 100)
                {
//saves the abomination position
                    place = abom.Position;
                    abom.health = 9999; //Edits abomination health to 9999
//* I explicitly do this so that the abomination doesn't die after the explosion.
// If the abomination dies before the .Destroy() method it will create a null Reference Error.
// After the explosion the code will then Destroy() the abomination.

//Do Explosion
                    Explosion.DoExplosion(place, 5f, DamageTypeDefOf.Bomb, null);

                    for (int b = 0; b < 250; b++) //Spews blood all over the place.
                    {
                        IntVec3 SpewBlood = GenCellFinder.RandomStandableClosewalkCellNear(place, 7);
                        ThingDef blood = DefDatabase<ThingDef>.GetNamed("FilthBlood");
                        FilthMaker.MakeFilth(SpewBlood, blood, 1);
                    }

//End Message.
                    string text = "The Mutated Abomination exploded to bits and pieces.";
                    Find.History.AddGameEvent(text, GameEventType.BadNonUrgent, true, place, string.Empty);

//Destroy abomination.
                    abom.Destroy();

//After the last event sequence you should then proceed to destroy this building for Clean Up.
                    this.Destroy();
                }
            }
           

        }
    }
}

//Rimworld
//Rimworld TechTreeMinami Mod by: minami26
//Rimworld


That's most of the code.
You then proceed to make the necessary XML for the event and the Building ThingDef XML for the MTcounter Building

ThingDef XML

<?xml version="1.0" encoding="utf-8" ?>
<Buildings>


<ThingDef Name="BuildingBase" Abstract="True">
<category>Building</category>
<soundBulletHit>BulletImpactMetal</soundBulletHit>
<selectable>true</selectable>
<drawerType>MapMeshAndRealTime</drawerType>
<surfaceNeeded>Light</surfaceNeeded>
<constructionEffect>ConstructMetal</constructionEffect>
<repairEffect>Repair</repairEffect>
<leaveResourcesWhenKilled>true</leaveResourcesWhenKilled>
</ThingDef>

<!-- Tech Tree Minami -->

<ThingDef ParentName="BuildingBase">
<defName>MTcounter</defName>
<eType>BuildingComplex</eType>
<label>Ticker</label>
<thingClass>TTMCustomEvents.Ticker_MTcounter</thingClass>
<textureFolderPath>Things/Filth/RubbleRock</textureFolderPath>
<altitudeLayer>Waist</altitudeLayer>
<passability>Standable</passability>
<selectable>false</selectable>
<useStandardHealth>false</useStandardHealth>
<tickerType>Normal</tickerType>
<description>Ticker</description>
<size>(1,1)</size>
<workToBuild>1</workToBuild>
<overdraw>false</overdraw>
<fillPercent>0</fillPercent>
<surfaceNeeded>Heavy</surfaceNeeded>
<designationCategory></designationCategory>
<itemSurface>true</itemSurface>
  </ThingDef>

</Buildings>


IncidentDef XML

<?xml version="1.0" encoding="utf-8" ?>
<IncidentDefs>

<!-- TTM Custom Events -->
<!-- Anomaly Events -->

<IncidentDef>
<defName>Anomaly_MysteriousTransmission</defName>
<workerClass>TTMCustomEvents.IncidentWorker_MysteriousTransmission</workerClass>
<minRefireDays>30</minRefireDays>
<favorability>Neutral</favorability>
<chance>2</chance>
</IncidentDef>

</IncidentDefs>


And thats it!
Oh and you might need the Abomination Pawn XML definitions too!
I'll put them in the attachments.

Thanks for checking this out!
More Power to RimWorld and Tynan Sylvester!

[attachment deleted by admin: too old]

mrofa

Cool thanks for sharing this and unirandome is not lame :D
All i do is clutter all around.

Telkir

This is useful for something I'm tinkering with too, thanks a lot!

Lord_Mortimus

I know this is 6 years old. But I'm at a road block and am throwing Hailmary's.

I've been trying to figure out how to make an IncidentDef for days when I found this post. Of course the IncidentDef no longer exists in the core these days. But does ANYONE know where it is now?
I'm trying to make a new event to spawn my own beetle creatures via my own beetle tunnel (effectively an insect hive).

RawCode

tutorial is fine but invalid

1) don't make garbage comments

Quote//These are references to RimWorld Code.

as good as
Quote//there are some letters
or
Quote//this is code

2) follow naming conventions

"face" and "landf" and "SpewBlood" are invalid variable names
when you set names for variables properly, you do not need 6 lines in row comments to explain your code

3) magical constants are magical constant for a reason, tutorials are expected to explain why tickrate is 60 per second, not perform some crazy match that ever child can do on it's own.
Also magical constants are expected to be const int MAGIC_TICK_RATE = 60; on top of your namespace, not inlined in random places.
check public static class GenTicks for more info about how it should be done
you see decompiled "sources" and const types are inlined by compiler, original source do not have magical constants in code, only single declaration.

4) your code does not take in account multiple maps that may exist, also your code may pick offscreen pawn.

5) proper implementation rely on custom MapComponent that have all required base methods.