FANDOM


(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.

The Code Changes Edit

CreatureTemplate Edit

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

AbstractCreature Edit

  • Create a new method, RealizeCustom():
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;
	}
}
  • Create a new method, RealizeCustomBranch():
public void RealizeCustomBranch()
{
	this.InitiateAI();
	this.RealizeCustom();
}
  • 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:
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);
}
  • 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 Edit

  • 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:
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);
  • 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 Edit

  • 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 Edit

  • 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 Edit

  • 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 Edit

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

MultiplayerUnlocks Edit

  • 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):
public static SandboxUnlockID[] CreatureUnlockList;
public static SandboxUnlockID[] ItemUnlockList;

RainWorld Edit

  • 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):
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
 	};

 	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 Edit

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 Edit

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 Edit

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<CreatureTemplate.Type>(unlockID.ToString()), AbstractPhysicalObject.AbstractObjectType.Creature, 0);
}

to:

foreach (MultiplayerUnlocks.SandboxUnlockID creature in MultiplayerUnlocks.CreatureUnlockList) {
 	if (unlockID == creature)
 	 	return new IconSymbol.IconSymbolData(Custom.ParseEnum<CreatureTemplate.Type>(unlockID.ToString()), AbstractPhysicalObject.AbstractObjectType.Creature, 0);
}

CreatureSymbol Edit

  • 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 Edit

  • Add performance estimation mapping to CreaturePerfEstimate()

SandboxSettingsInterface Edit

  • 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.
Community content is available under CC-BY-SA unless otherwise noted.