Adding a Custom Creature

(Note: This page was made for Rain World v1.01 and has not been fully updated for v1.5)

This page lists essential code changes you must make to add a custom creature to the game (so that it is identified by the game as an entirely new and separate entity). Generally creatures at the very least will have a core, graphics, and artificial intelligence class. For this tutorial we'll use names for these like TestCreature, TestCreatureGraphics, and TestCreatureAI. You'll have to code all of these yourself, it's very complicated, sorry! For your first creature I suggest copy, pasting, and modifying an existing creature's core classes to help you out. There are a lot of interdependencies between these classes, so I suggest compiling them all together in an external text file and then adding them into dnSpy all at the same time. Adding each class one-by-one will likely not be possible without running into compile errors.

CreatureTemplate
case CreatureTemplate.Type.TestCreature: this.name = "Test Creature"; break;
 * Add your creature as a new entry in the Type enum.
 * In the constructor, add your creature to the switch statement:

AbstractCreature
public void RealizeCustom { 	if (this.realizedCreature != null) { 		return; } 	switch (this.creatureTemplate.TopAncestor.type) { 	case CreatureTemplate.Type.TestCreature: this.realizedCreature = new TestCreature(this, this.world); this.abstractAI.RealAI = new TestCreatureAI(this, this.world); break; } } public void RealizeCustomBranch { 	this.InitiateAI; this.RealizeCustom; } if (type == CreatureTemplate.Type.Fly || type == CreatureTemplate.Type.Leech || type == CreatureTemplate.Type.Spider) this.state = new NoHealthState(null); else if (type == CreatureTemplate.Type.Vulture) this.state = new Vulture.VultureState(null); else if (type == CreatureTemplate.Type.GarbageWorm) { this.remainInDenCounter = 0; GarbageWormAI.MoveAbstractCreatureToGarbage(null, base.Room); this.state = new GarbageWormState(null, Mathf.Lerp(0.35f, 1f, Mathf.Pow(world.game.SeededRandom(ID.RandomSeed), 0.5f))); } else if (type == CreatureTemplate.Type.LanternMouse) this.state = new MouseState(null); else if (type == CreatureTemplate.Type.LizardTemplate) this.state = new LizardState(null); else if (type == CreatureTemplate.Type.DaddyLongLegs) this.state = new DaddyLongLegs.DaddyState(null); else if (type == CreatureTemplate.Type.VultureGrub) this.state = new VultureGrub.VultureGrubState(null); else if (type == CreatureTemplate.Type.TestCreature) this.state = new TestCreature.TestCreatureState(null); else if (type != CreatureTemplate.Type.Slugcat) this.state = new HealthState(null); if (creatureTemplate.AI) { type = creatureTemplate.type; if (type == CreatureTemplate.Type.Vulture) this.abstractAI = new VultureAbstractAI(world, null); else if (type == CreatureTemplate.Type.CicadaA || type == CreatureTemplate.Type.CicadaB) this.abstractAI = new CicadaAbstractAI(world, null); else if (type == CreatureTemplate.Type.BigEel) this.abstractAI = new BigEelAbstractAI(world, null); else if (type == CreatureTemplate.Type.Deer) this.abstractAI = new DeerAbstractAI(world, null); else if (type == CreatureTemplate.Type.Scavenger) this.abstractAI = new ScavengerAbstractAI(world, null); else if (type == CreatureTemplate.Type.Overseer) this.abstractAI = new OverseerAbstractAI(world, null); else if (type == CreatureTemplate.Type.MirosBird) this.abstractAI = new MirosBirdAbstractAI(world, null); else if (type == CreatureTemplate.Type.TestCreature) this.abstractAI = new TestCreatureAbstractAI(world, null); else this.abstractAI = new AbstractCreatureAI(world, null); }
 * Create a new method, RealizeCustom:
 * Create a new method, RealizeCustomBranch:
 * Use Edit IL to change the call to "InitiateAI" inside the Realize function, so that it calls your RealizeCustomBranch function instead.
 * Add your creature state, and optionally its abstract AI class if it has one, to the constructor. You'll have to change all the "this" arguments to "null" like before. Also dnSpy doesn't like the switch statements in this constructor, so here's all those switch statements reformatted as if-else, for your convenience:
 * Again, like before, use IL Edit to change all the "null"s back to "this". There's a lot of them this time. Grab a coffee. Throw on some music. It'll be a while.

WorldLoader
if (s == "Pink") return new CreatureTemplate.Type?(CreatureTemplate.Type.PinkLizard); if (s == "Green") return new CreatureTemplate.Type?(CreatureTemplate.Type.GreenLizard); if (s == "Blue") return new CreatureTemplate.Type?(CreatureTemplate.Type.BlueLizard); if (s == "Yellow") return new CreatureTemplate.Type?(CreatureTemplate.Type.YellowLizard); if (s == "White") return new CreatureTemplate.Type?(CreatureTemplate.Type.WhiteLizard); if (s == "Red") return new CreatureTemplate.Type?(CreatureTemplate.Type.RedLizard); if (s == "Black") return new CreatureTemplate.Type?(CreatureTemplate.Type.BlackLizard); if (s == "Cyan") return new CreatureTemplate.Type?(CreatureTemplate.Type.CyanLizard); if (s == "Leech") return new CreatureTemplate.Type?(CreatureTemplate.Type.Leech); if (s == "SeaLeech") return new CreatureTemplate.Type?(CreatureTemplate.Type.SeaLeech); if (s == "Snail") return new CreatureTemplate.Type?(CreatureTemplate.Type.Snail); if (s == "Vulture") return new CreatureTemplate.Type?(CreatureTemplate.Type.Vulture); if (s == "CicadaA") return new CreatureTemplate.Type?(CreatureTemplate.Type.CicadaA); if (s == "CicadaB") return new CreatureTemplate.Type?(CreatureTemplate.Type.CicadaB); if (s == "Cicada") return new CreatureTemplate.Type?((UnityEngine.Random.value >= 0.5f) ? CreatureTemplate.Type.CicadaB : CreatureTemplate.Type.CicadaA); if (s == "Lantern Mouse" || s == "Mouse") return new CreatureTemplate.Type?(CreatureTemplate.Type.LanternMouse); if (s == "Spider") return new CreatureTemplate.Type?(CreatureTemplate.Type.Spider); if (s == "Worm" || s == "Garbage Worm") return new CreatureTemplate.Type?(CreatureTemplate.Type.GarbageWorm); if (s == "Leviathan" || s == "Lev") return new CreatureTemplate.Type?(CreatureTemplate.Type.BigEel); if (s == "Tube" || s == "TubeWorm") return new CreatureTemplate.Type?(CreatureTemplate.Type.TubeWorm); if (s == "Daddy" || s == "DaddyLongLegs") return new CreatureTemplate.Type?(CreatureTemplate.Type.DaddyLongLegs); if (s == "Bro" || s == "BroLongLegs") return new CreatureTemplate.Type?(CreatureTemplate.Type.BrotherLongLegs); if (s == "TentaclePlant" || s == "Tentacle") return new CreatureTemplate.Type?(CreatureTemplate.Type.TentaclePlant); if (s == "PoleMimic" || s == "Mimic") return new CreatureTemplate.Type?(CreatureTemplate.Type.PoleMimic); if (s == "MirosBird" || s == "Miros") return new CreatureTemplate.Type?(CreatureTemplate.Type.MirosBird); if (s == "Centipede" || s == "Cent") return new CreatureTemplate.Type?(CreatureTemplate.Type.Centipede); if (s == "JetFish" || s == "Jetfish") return new CreatureTemplate.Type?(CreatureTemplate.Type.JetFish); if (s == "Eggbug" || s == "EggBug") return new CreatureTemplate.Type?(CreatureTemplate.Type.EggBug); if (s == "BigSpider") return new CreatureTemplate.Type?(CreatureTemplate.Type.BigSpider); if (s == "SpitterSpider") return new CreatureTemplate.Type?(CreatureTemplate.Type.SpitterSpider); if (s == "BigNeedle" || s == "Needle" || s == "Needle Worm") return new CreatureTemplate.Type?(CreatureTemplate.Type.BigNeedleWorm); if (s == "SmallNeedle") return new CreatureTemplate.Type?(CreatureTemplate.Type.SmallNeedleWorm); if (s == "DropBug" || s == "Dropbug" || s == "DropWig" || s == "Dropwig") return new CreatureTemplate.Type?(CreatureTemplate.Type.DropBug); if (s == "KingVulture" || s == "King Vulture") return new CreatureTemplate.Type?(CreatureTemplate.Type.KingVulture); if (s == "Red Centipede" || s == "RedCentipede" || s == "RedCenti") return new CreatureTemplate.Type?(CreatureTemplate.Type.RedCentipede);
 * Add your creature to the CreatureTypeFromString function. Again, dnSpy does not play well with the switch statement in this function, so here's the switch statement reformatted as an if-else chain again:
 * The string you specify here is the string you will have to type in the world_XX.txt file when defining the spawn of your creature in a map.

StaticWorld

 * Add your creature to the StaticWorld definitions. Copy and paste from an existing creature as a template if you need to. Make sure you define all three lists for your creature. (Where list2 = TileTypeResistances, list3 = TileConnectionResistances, list = CreatureTemplate properties, requires list2/list3 defined first)

MapPage

 * Add your creature to the CritString function. This is the text acronym that is used for your creature when it's shown as an icon in the Dev Mode map tab.
 * Optionally add your creature to the CritCol function. This is the color the text acronym will show up as in the map tab in Dev Mode. If you don't define it, it'll default to gray.

RoomRealizer

 * Add the estimated performance cost of your creature to the RoomPerformanceEstimation function here. You can use the other creature values as a base line. Generally the more sprites your creature is drawing in its graphics class, the more impact it'll have on performance, so probably judge it based on that.

AImap

 * Optionally, if your creature has certain terrain movement restrictions, you may have to define these here in the TileAccessibleToCreature function.

MultiplayerUnlocks
public static SandboxUnlockID[] CreatureUnlockList; public static SandboxUnlockID[] ItemUnlockList;
 * Add your new creature to the SandboxUnlockID enum. It’s important that the name used for the creature here is the same as the name you used in the CreatureType enum. You’ll want to make a note of the numerical ID of this new entry in the enum, you’ll need it later.
 * Create two new arrays (one time change):

RainWorld
private void Awake { MultiplayerUnlocks.CreatureUnlockList = new MultiplayerUnlocks.SandboxUnlockID[] { 	 	MultiplayerUnlocks.SandboxUnlockID.Slugcat, MultiplayerUnlocks.SandboxUnlockID.GreenLizard, MultiplayerUnlocks.SandboxUnlockID.PinkLizard, MultiplayerUnlocks.SandboxUnlockID.BlueLizard, MultiplayerUnlocks.SandboxUnlockID.WhiteLizard, MultiplayerUnlocks.SandboxUnlockID.BlackLizard, MultiplayerUnlocks.SandboxUnlockID.YellowLizard, MultiplayerUnlocks.SandboxUnlockID.CyanLizard, MultiplayerUnlocks.SandboxUnlockID.RedLizard, MultiplayerUnlocks.SandboxUnlockID.Salamander, MultiplayerUnlocks.SandboxUnlockID.Fly, MultiplayerUnlocks.SandboxUnlockID.CicadaA, MultiplayerUnlocks.SandboxUnlockID.CicadaB, MultiplayerUnlocks.SandboxUnlockID.Snail, MultiplayerUnlocks.SandboxUnlockID.Leech, MultiplayerUnlocks.SandboxUnlockID.SeaLeech, MultiplayerUnlocks.SandboxUnlockID.PoleMimic, MultiplayerUnlocks.SandboxUnlockID.TentaclePlant, MultiplayerUnlocks.SandboxUnlockID.Scavenger, MultiplayerUnlocks.SandboxUnlockID.VultureGrub, MultiplayerUnlocks.SandboxUnlockID.Vulture, MultiplayerUnlocks.SandboxUnlockID.KingVulture, MultiplayerUnlocks.SandboxUnlockID.SmallCentipede, MultiplayerUnlocks.SandboxUnlockID.MediumCentipede, MultiplayerUnlocks.SandboxUnlockID.BigCentipede, MultiplayerUnlocks.SandboxUnlockID.RedCentipede, MultiplayerUnlocks.SandboxUnlockID.Centiwing, MultiplayerUnlocks.SandboxUnlockID.TubeWorm, MultiplayerUnlocks.SandboxUnlockID.Hazer, MultiplayerUnlocks.SandboxUnlockID.LanternMouse, MultiplayerUnlocks.SandboxUnlockID.Spider, MultiplayerUnlocks.SandboxUnlockID.BigSpider, MultiplayerUnlocks.SandboxUnlockID.SpitterSpider, MultiplayerUnlocks.SandboxUnlockID.MirosBird, MultiplayerUnlocks.SandboxUnlockID.BrotherLongLegs, MultiplayerUnlocks.SandboxUnlockID.DaddyLongLegs, MultiplayerUnlocks.SandboxUnlockID.Deer, MultiplayerUnlocks.SandboxUnlockID.EggBug, MultiplayerUnlocks.SandboxUnlockID.DropBug, MultiplayerUnlocks.SandboxUnlockID.BigNeedleWorm, MultiplayerUnlocks.SandboxUnlockID.SmallNeedleWorm, MultiplayerUnlocks.SandboxUnlockID.Jetfish, MultiplayerUnlocks.SandboxUnlockID.BigEel };
 * Add an Awake function if it does not exist already. In the Awake function, add the following array definitions (with your new creature added to the creature list):

MultiplayerUnlocks.ItemUnlockList = new MultiplayerUnlocks.SandboxUnlockID[] { 	 	MultiplayerUnlocks.SandboxUnlockID.Rock, MultiplayerUnlocks.SandboxUnlockID.Spear, MultiplayerUnlocks.SandboxUnlockID.FireSpear, MultiplayerUnlocks.SandboxUnlockID.ScavengerBomb, MultiplayerUnlocks.SandboxUnlockID.SporePlant, MultiplayerUnlocks.SandboxUnlockID.Lantern, MultiplayerUnlocks.SandboxUnlockID.FlyLure, MultiplayerUnlocks.SandboxUnlockID.Mushroom, MultiplayerUnlocks.SandboxUnlockID.FlareBomb, MultiplayerUnlocks.SandboxUnlockID.PuffBall, MultiplayerUnlocks.SandboxUnlockID.WaterNut, MultiplayerUnlocks.SandboxUnlockID.FirecrackerPlant, MultiplayerUnlocks.SandboxUnlockID.DangleFruit, MultiplayerUnlocks.SandboxUnlockID.JellyFish, MultiplayerUnlocks.SandboxUnlockID.BubbleGrass, MultiplayerUnlocks.SandboxUnlockID.SlimeMold }; }

SandboxEditorSelector
In Constructor, change: for (int j = MultiplayerUnlocks.CreaturesUnlocks; j < MultiplayerUnlocks.CreaturesUnlocks + MultiplayerUnlocks.ItemsUnlocks; j++) { 	if (this.unlocks.SandboxItemUnlocked((MultiplayerUnlocks.SandboxUnlockID)j)) { 	 	this.AddButton(new SandboxEditorSelector.CreatureOrItemButton(menu, this, MultiplayerUnlocks.SymbolDataForSandboxUnlock((MultiplayerUnlocks.SandboxUnlockID)j)), ref num); } 	else { 	 	this.AddButton(new SandboxEditorSelector.LockedButton(menu, this), ref num); } } for (int k = 0; k < MultiplayerUnlocks.CreaturesUnlocks; k++) { 	if (this.unlocks.SandboxItemUnlocked((MultiplayerUnlocks.SandboxUnlockID)k)) { 	 	this.AddButton(new SandboxEditorSelector.CreatureOrItemButton(menu, this, MultiplayerUnlocks.SymbolDataForSandboxUnlock((MultiplayerUnlocks.SandboxUnlockID)k)), ref num); } 	else { 	 	this.AddButton(new SandboxEditorSelector.LockedButton(menu, this), ref num); } } to: foreach (MultiplayerUnlocks.SandboxUnlockID item in MultiplayerUnlocks.ItemUnlockList) { if (this.unlocks.SandboxItemUnlocked(item)) this.AddButton(new SandboxEditorSelector.CreatureOrItemButton(menu, this, MultiplayerUnlocks.SymbolDataForSandboxUnlock(item)), ref num); else this.AddButton(new SandboxEditorSelector.LockedButton(menu, this), ref num); } foreach (MultiplayerUnlocks.SandboxUnlockID creature in MultiplayerUnlocks.CreatureUnlockList) { if (this.unlocks.SandboxItemUnlocked(creature)) this.AddButton(new SandboxEditorSelector.CreatureOrItemButton(menu, this, MultiplayerUnlocks.SymbolDataForSandboxUnlock(creature)), ref num); else this.AddButton(new SandboxEditorSelector.LockedButton(menu, this), ref num); }

SandboxSettingsInterface
In Constructor, change: for (int i = 0; i < MultiplayerUnlocks.CreaturesUnlocks; i++) { 	if (i != 10 && i != 14 && i != 15 && i != 40 && i != 30 && i != 19 && i != 42 && i != 36 && i!= 22 && i != 27 && i != 28) { 	 	this.AddScoreButton((MultiplayerUnlocks.SandboxUnlockID)i, ref intVector); } } to: foreach (MultiplayerUnlocks.SandboxUnlockID creature in MultiplayerUnlocks.CreatureUnlockList) { if (creature != MultiplayerUnlocks.SandboxUnlockID.Fly && 	creature != MultiplayerUnlocks.SandboxUnlockID.Leech && 	creature != MultiplayerUnlocks.SandboxUnlockID.SeaLeech && 	creature != MultiplayerUnlocks.SandboxUnlockID.SmallNeedleWorm && 	creature != MultiplayerUnlocks.SandboxUnlockID.Spider && 	creature != MultiplayerUnlocks.SandboxUnlockID.VultureGrub && 	creature != MultiplayerUnlocks.SandboxUnlockID.BigEel && 	creature != MultiplayerUnlocks.SandboxUnlockID.Deer && 	creature != MultiplayerUnlocks.SandboxUnlockID.SmallCentipede && 	creature != MultiplayerUnlocks.SandboxUnlockID.TubeWorm && 	creature != MultiplayerUnlocks.SandboxUnlockID.Hazer) this.AddScoreButton(creature, ref intVector); }

MultiplayerUnlocks
In Constructor, change: for (int l = 0; l < MultiplayerUnlocks.CreaturesUnlocks; l++) { 	if (this.SandboxItemUnlocked((MultiplayerUnlocks.SandboxUnlockID)l)) { 	 	this.creaturesUnlockedForLevelSpawn[(int)MultiplayerUnlocks.SymbolDataForSandboxUnlock( (MultiplayerUnlocks.SandboxUnlockID)l).critType] = true; } } to: foreach (MultiplayerUnlocks.SandboxUnlockID creature in MultiplayerUnlocks.CreatureUnlockList) { if (this.SandboxItemUnlocked(creature)) this.creaturesUnlockedForLevelSpawn[(int)MultiplayerUnlocks.SymbolDataForSandboxUnlock(creature).critType] = true; } In SymbolDataForSandboxUnlock, change: if (unlockID < (MultiplayerUnlocks.SandboxUnlockID)MultiplayerUnlocks.CreaturesUnlocks) { 	return new IconSymbol.IconSymbolData(Custom.ParseEnum(unlockID.ToString), AbstractPhysicalObject.AbstractObjectType.Creature, 0); } to: foreach (MultiplayerUnlocks.SandboxUnlockID creature in MultiplayerUnlocks.CreatureUnlockList) { if (unlockID == creature) return new IconSymbol.IconSymbolData(Custom.ParseEnum(unlockID.ToString), AbstractPhysicalObject.AbstractObjectType.Creature, 0); }

CreatureSymbol

 * Add an icon for your creature to the uiSprites texture atlas.
 * Add the sprite name mapping to SpriteNameOfCreature
 * Set the color of the icon in ColorOfCreature

SandboxEditor

 * Add performance estimation mapping to CreaturePerfEstimate

SandboxSettingsInterface

 * Add default point value for killing the creature to DefaultKillScores. The numerical index for the creature is the same as its position in the SandboxUnlockID enum.