Skip to content
John Snail edited this page Sep 9, 2024 · 14 revisions

Welcome to the Subworld Library wiki!

Getting Started

Simply open your mod's build.txt, and add modReferences = SubworldLibrary.

It is highly recommended that you also download SubworldLibrary.dll and SubworldLibrary.xml. These let you reference Subworld Library and view its documentation in your IDE.
Visual Studio: right-click on Dependencies in your project, click on Add Project Reference..., then Browse..., select the dll, and click OK. The xml will be detected automatically if it's in the same location as the dll.

Creating Subworlds

Classes that derive from Subworld are automatically registered as subworlds. Below is a list of fields and methods that can be overridden.

Width The subworld's width.
Height The subworld's height.
Tasks The subworld's generation tasks.
ShouldSave Whether the subworld should save or not. Default: false
NoPlayerSaving Reverts changes to players when they leave the subworld. Default: false
NormalUpdates Completely disables vanilla world updating in the subworld. Do not enable unless you are replicating a standard world! Default: false
OnEnter() Called when entering a subworld. Before this is called, noReturn and hideUnderworld are reset.
OnLoad() Called after the subworld generates or loads from file.
OnExit() Called when exiting a subworld. After this is called, noReturn and hideUnderworld are reset.
OnUnload() Called while leaving the subworld, before a different world generates or loads from file.
DrawMenu() Called by DrawSetup to draw the subworld's loading menu. Defaults to text on a black background.
Main.spriteBatch.DrawString(FontAssets.DeathText.Value, Main.statusText, new Vector2(Main.screenWidth, Main.screenHeight) / 2 - FontAssets.DeathText.Value.MeasureString(Main.statusText) / 2, Color.White);
DrawSetup() Corrects zoom and clears the screen, then calls DrawMenu and draws the cursor.
PlayerInput.SetZoom_Unscaled();
Main.instance.GraphicsDevice.Clear(Color.Black);
Main.spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.None, Main.Rasterizer, null, Main.UIScaleMatrix);
DrawMenu(gameTime);
Main.DrawCursor(Main.DrawThickCursor());
Main.spriteBatch.End();
GetLight() Controls how a tile in the subworld is lit. Return true to disable vanilla behaviour. Default: false
// Gives water a blue glow.
if (tile.LiquidType == LiquidID.Water && tile.LiquidAmount > 0)
{
	color.Z = tile.LiquidAmount / 255f; // The result of integer division is rounded down, so one of the numbers must be a float
}
return false;

Naming a Subworld

Typically, a subworld's name is seen in Multiplayer, and is set with the following localization key: SubworldName.[subworld class] (e.g. SubworldName.ExampleSubworld)

Entering and Exiting a Subworld

To enter a subworld, call SubworldSystem.Enter<T>(), where T is the subworld's class.
To exit a subworld, call SubworldSystem.Exit().

To enter another mod's subworld:
If the mod is a dependency, call SubworldSystem.Enter<T>().
If the mod is not a dependency, call SubworldSystem.Enter(string), where string is the mod's ID, followed by /, followed by the subworld's ID (e.g. "ExampleMod/ExampleSubworld").

SubworldSystem

Current The current subworld.
IsActive(string) Returns true if the current subworld's ID matches the specified ID.
IsActive<T>() Where T is the subworld's class. Returns true if the specified subworld is active.
AnyActive(Mod) Returns true if the current subworld is from the specified mod.
AnyActive<T>() Where T is the mod's class. Returns true if the current subworld is from the specified mod.
noReturn Hides the Return button. Its value is reset before OnEnter is called, and after OnExit is called.
public override void OnEnter()
{
	SubworldSystem.noReturn = true;
}
hideUnderworld Hides the Underworld background. Its value is reset before OnEnter is called, and after OnExit is called.
public override void OnEnter()
{
	SubworldSystem.hideUnderworld = false;
}

Examples

Basic Subworld

public class ExampleSubworld : Subworld
{
	public override int Width => 1000;
	public override int Height => 1000;

	public override bool ShouldSave => false;
	public override bool NoPlayerSaving => true;

	public override List<GenPass> Tasks => new List<GenPass>()
	{
		new ExampleGenPass()
	};

	// Sets the time to the middle of the day whenever the subworld loads
	public override void OnLoad()
	{
		Main.dayTime = true;
		Main.time = 27000;
	}
}
public class ExampleGenPass : GenPass
{
	//TODO: remove this once tML changes generation passes
	public ExampleGenPass() : base("Terrain", 1) { }

	protected override void ApplyPass(GenerationProgress progress, GameConfiguration configuration)
	{
		progress.Message = "Generating terrain"; // Sets the text displayed for this pass
		Main.worldSurface = Main.maxTilesY - 42; // Hides the underground layer just out of bounds
		Main.rockLayer = Main.maxTilesY; // Hides the cavern layer way out of bounds
		for (int i = 0; i < Main.maxTilesX; i++)
		{
			for (int j = 0; j < Main.maxTilesY; j++)
			{
				progress.Set((j + i * Main.maxTilesY) / (float)(Main.maxTilesX * Main.maxTilesY)); // Controls the progress bar, should only be set between 0f and 1f
				Tile tile = Main.tile[i, j];
				tile.HasTile = true;
				tile.TileType = TileID.Dirt;
			}
		}
	}
}

Updating Subworlds

This example shows how to update mechanisms, tile entities and liquid in subworlds that don't update normally.

public class UpdateSubworldSystem : ModSystem
{
	public override void PreUpdateWorld()
	{
		if (SubworldSystem.IsActive<ExampleSubworld>())
		{
			// Update mechanisms
			Wiring.UpdateMech();

			// Update tile entities
			TileEntity.UpdateStart();
			foreach (TileEntity te in TileEntity.ByID.Values)
			{
				te.Update();
			}
			TileEntity.UpdateEnd();

			// Update liquid
			if (++Liquid.skipCount > 1)
			{
				Liquid.UpdateLiquid();
				Liquid.skipCount = 0;
			}
		}
	}
}

Setting a subworld's seed

By default, subworlds inherit the seed of the main world. This behaviour can be overridden by calling Main.ActiveWorldFileData.SetSeed in the subworld's first GenPass.

public override List<GenPass> Tasks => new List<GenPass>()
{
	new SeedGenPass(),
	new ExampleGenPass()
	// other passes
};
public class SeedGenPass : GenPass
{
	//TODO: remove this once tML changes generation passes
	public SeedGenPass() : base("Set Seed", 0.01f) { }

	protected override void ApplyPass(GenerationProgress progress, GameConfiguration configuration)
	{
		progress.Message = "Setting subworld seed"; // Sets the text displayed for this pass
		Main.ActiveWorldFileData.SetSeedToRandom(); // Randomizes the subworld seed
		//Main.ActiveWorldFileData.SetSeed(100.ToString()); // Sets the subworld seed to 100

		// You can do other things in this pass as long as they don't make RNG calls! Having specific passes is cleaner though.
	}
}

Replicating standard worlds

public class StandardWorldGenPass : GenPass
{
	//TODO: remove this once tML changes generation passes
	public StandardWorldGenPass() : base("Standard World", 100) { }

	protected override void ApplyPass(GenerationProgress progress, GameConfiguration configuration)
	{
		GenerationProgress cache = WorldGenerator.CurrentGenerationProgress; // Cache generation progress...
		WorldGen.GenerateWorld(Main.ActiveWorldFileData.Seed);
		WorldGenerator.CurrentGenerationProgress = cache; // ...because GenerateWorld sets it to null when it ends, and it must be set back
	}
}