feat: add initial People Playground mod development kit

This commit is contained in:
2026-01-06 06:35:51 +03:00
parent b89c805060
commit 566791ce62
1095 changed files with 40280 additions and 1 deletions

View File

@@ -0,0 +1,76 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomBehaviour.html
Title: People Playground Modding - Creating a custom behaviour
==================================================
Creating a custom behaviour
You can create your own MonoBehaviours, meaning you can do pretty much anything you want if you try.
This tutorial will teach you how to create a simple behaviour and add it to your custom item (that you hopefully created in the previous tutorial).
1. Creating a behaviour class
Pretty simple really, especially if you know anything about Unity. You may read the Unity Documentation for this class if you want to know more.
All you need to do is define a class that inherits MonoBehaviour inside the namespace you created. This can be in the script.cs file.
public class WhateverBehaviour : MonoBehaviour
{
private void Start()
{
//Do whatever you want here
//I guess we'll make it explode when spawned
ExplosionCreator.CreateFragmentationExplosion(
32,
transform.position,
4,
7,
true,
false,
0.5f);
}
}
2. Adding the component to your item
Also really easy to do. All that needs to be done is adding this line in your AfterSpawn function:
Instance.AddComponent<WhateverBehaviour>();
This will add the given component to the GameObject.
If everything went well, your script.cs file will look like this:
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Brick"),
NameOverride = "Blue Brick lmfao -BlueBrick",
DescriptionOverride = "Like a brick but differently coloured.",
CategoryOverride = ModAPI.FindCategory("Misc."),
ThumbnailOverride = ModAPI.LoadSprite("blueBrickView.png"),
AfterSpawn = (Instance) =>
{
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("blueBrick.png");
Instance.AddComponent<WhateverBehaviour>();
}
}
);
}
}
public class WhateverBehaviour : MonoBehaviour
{
private void Start()
{
ExplosionCreator.CreateFragmentationExplosion(
32,
transform.position,
4,
7,
true,
false,
0.5f);
}
}
}
When you spawn the item you added, it should explode.

View File

@@ -0,0 +1,66 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomItem.html
Title: People Playground Modding - Creating a custom item
==================================================
Creating a custom item
This guide will teach you how to add a custom item in your mod. Preferably, you should follow Creating a mod first.
Your mod folder should now only contain a mod.json file and a script.cs, which should look like this:
using UnityEngine; //You'll probably need this...
namespace Mod
{
public class Mod
{
public static void Main()
{
//This method is the entry point.
}
}
}
Adding a new item is really quite simple. All you need to do is call the ModAPI.Register function and pass the appropriate arguments.
This snippet of code is (kind of) from the snippets repository, but it is a very clear example of what the Register function expects.
// register item to the mod api
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Brick"), //item to derive from
NameOverride = "Blue Brick lmfao -BlueBrick", //new item name with a suffix to assure it is globally unique
DescriptionOverride = "Like a brick but differently coloured.", //new item description
CategoryOverride = ModAPI.FindCategory("Misc."), //new item category
ThumbnailOverride = ModAPI.LoadSprite("blueBrickView.png"), //new item thumbnail (relative path)
AfterSpawn = (Instance) => //all code in the AfterSpawn delegate will be executed when the item is spawned
{
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("blueBrick.png"); //get the SpriteRenderer and replace its sprite with a custom one
}
}
);
Now to put this in your Main function is super straightforward:
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Brick"),
NameOverride = "Blue Brick lmfao -BlueBrick",
DescriptionOverride = "Like a brick but differently coloured.",
CategoryOverride = ModAPI.FindCategory("Misc."),
ThumbnailOverride = ModAPI.LoadSprite("blueBrickView.png"), //Doesn't exist yet!
AfterSpawn = (Instance) =>
{
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("blueBrick.png"); //Doesn't exist yet!
}
}
);
}
}
}
When this is done, all you need to do now is actually create the blueBrickView.png and blueBrick.png images that are referenced in the code. An item thumbnail should be a 256x256 PNG image, and game sprites can be whatever size you want. It is recommended to have a regular in-game item sprite by your side to make sure you are scaling your item correctly. These files have to exist for the mod to work, so don't continue without creating these. They should be located in your mod folder.

View File

@@ -0,0 +1,97 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomLiquid.html
Title: People Playground Modding - Creating a custom liquid
==================================================
Creating a custom liquid
1. Defining your liquid
A liquid is a class that is only instantiated once.
All liquids derive from the Liquid class.
This class is abstract and requires the implementation of the following:
// Called when this liquid enters a limb. Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public abstract void OnEnterLimb(LimbBehaviour limb);
// Called when this liquid enters a container. Limbs are also containers. Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public abstract void OnEnterContainer(BloodContainer container);
// Called when this liquid exits a container. Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public abstract void OnExitContainer(BloodContainer container);
These are required. They can be left empty if there is nothing to do. A few optional members can also be implemented that provide additional functionality that your liquid may require.
1.1 OnUpdate
The OnUpdate method is an empty virtual method that can be overridden. It's called every second for every liquid inside every container. The relevant BloodContainer is given.
A common implementation looks like this:
public override void OnUpdate(BloodContainer container)
{
base.OnUpdate(container); //just in case whatever you're deriving from does anything
if (container is CirculationBehaviour circ) //is the container a limb?
{
var limb = circ.Limb;
//do stuff with limb
}
}
1.2 GetDisplayName
This is a getter method that should return the display name of your liquid. This string will be displayed wherever your liquid is supposed to be represented by text, most notably in the Liquidentifier. If this method is unimplemented, it will return the liquid ID string. If the liquid hasn't even been registered, the base implementation will return the type name.
Example implementation of this method:
public override string GetDisplayName() => "Human blood";
1.3 Full example
As a full example, this is coagulation serum:
public class CoagulationLiquid : TemporaryBodyLiquid
{
public const string ID = "COAGULATION SERUM";
public override string GetDisplayName() => "Coagulation serum";
public CoagulationLiquid()
{
//The color of the liquid is usually set in the constructor, but can be changed anytime.
//Do note, however, that liquid containers cache their liquid colour for performance reasons.
//Changing the colour outside the constructor is not recommended.
Color = new UnityEngine.Color(1, 1, 0);
}
public override void OnEnterContainer(BloodContainer container)
{
}
public override void OnEnterLimb(LimbBehaviour limb)
{
}
public override void OnUpdate(BloodContainer c)
{
base.OnUpdate(c);
if (c is CirculationBehaviour circ)
{
var limb = circ.Limb;
if (limb.SpeciesIdentity == Species.Android)
return;
limb.CirculationBehaviour.HealBleeding();
}
}
public override void OnExitContainer(BloodContainer container)
{
}
}
2. Registering a custom liquid
Before any liquid can be used, it has to be registered to the liquid registry. More information here.
To summarise, all liquids have a unique ID string. This is usually some variation of the liquid name, but it can be a completely random string too. Just make sure it's globally unique.
In short, add ModAPI.RegisterLiquid(DurabilitySyringe.DurabilitySerum.ID, new DurabilitySyringe.DurabilitySerum()); before anything in your Main method, like so:
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
ModAPI.RegisterLiquid(DurabilitySyringe.DurabilitySerum.ID, new DurabilitySyringe.DurabilitySerum());
...

View File

@@ -0,0 +1,193 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomSyringe.html
Title: People Playground Modding - Creating a custom syringe
==================================================
Creating a custom syringe
A syringe is a liquid container. Most syringes consist of two components: the syringe behaviour and the liquid definition.
1. Creating a syringe spawnable
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
// registering a custom item
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Acid Syringe"), // derive from a syringe
NameOverride = "Nitroglycerin Syringe [mod name]",
DescriptionOverride = "Syringe full of nitroglycerin.",
CategoryOverride = ModAPI.FindCategory("Biohazard"),
ThumbnailOverride = ModAPI.LoadSprite("nitro syringe view.png"),
AfterSpawn = (Instance) =>
{
// destroy the existing syringe behaviour
UnityEngine.Object.Destroy(Instance.GetComponent<SyringeBehaviour>());
// add the new syringe behaviour (unless it already exists)
Instance.GetOrAddComponent<NitroglycerinSyringe>();
}
}
);
}
}
// define the syringe behaviour
public class NitroglycerinSyringe : SyringeBehaviour
{
// provide the liquid ID for this syringe
public override string GetLiquidID() => Nitroglycerine.ID;
}
}
All syringes have a unique behaviour associated with them. This isn't strictly necessary but it allows for other unique effects, mostly taken advantage of by the Pink Syringe.
2. Creating a custom liquid
In the above section, we created a syringe full of an existing liquid. You can define your own liquid, with its own effects, and use that instead.
Instead of a nitroglycerin syringe, let's add a syringe that makes limbs unbreakable.
First, change the spawnable definition.
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Acid Syringe"),
NameOverride = "Durability Syringe [mod name]", // different name
DescriptionOverride = "Syringe full of an enhancing serum that makes limbs unbreakable.", // different description
CategoryOverride = ModAPI.FindCategory("Biohazard"),
ThumbnailOverride = ModAPI.LoadSprite("durability syringe view.png"), //different thumbnail
AfterSpawn = (Instance) =>
{
UnityEngine.Object.Destroy(Instance.GetComponent<SyringeBehaviour>());
// different behaviour. we will make this in a moment.
Instance.GetOrAddComponent<DurabilitySyringe>();
}
}
Then we change the syringe behaviour we created. Here's a convenient colour picker.
public class DurabilitySyringe : SyringeBehaviour
{
// provide the liquid ID for this syringe
public override string GetLiquidID() => DurabilitySerum.ID;
// we can make it a nested class for extra localisation points
public class DurabilitySerum : Liquid // if you wanted this effect to be temporary, you can derive from TemporaryBodyLiquid instead
{
// declare a globally unique ID for this liquid
public const string ID = "DURABILITY SERUM [mod name]";
public DurabilitySerum()
{
// assign the colour of the liquid. a pleasant yellow-green for this one
Color = new UnityEngine.Color(0.69f, 1f, 0.35f);
}
//Called when this liquid enters a limb.
//Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public override void OnEnterLimb(LimbBehaviour limb)
{
//this is where syringes usually apply their effects.
limb.BreakingThreshold = 10000;
}
//Called every second by every container for every liquid it contains.
public override void OnUpdate(BloodContainer container)
{
//for syringe liquids, this is usually empty.
//if you want a syringe effect to be continuously applied, you should implement the effects here instead.
//make sure to check if the given container is actually a CirculationBehaviour.
//do note that this has to call base.OnUpdate(container) if you are derving from TemporaryBodyLiquid
}
//Called when this liquid enters a container. Limbs are also containers.
//Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public override void OnEnterContainer(BloodContainer container)
{
//for syringe liquids, this is usually empty.
}
//Called when this liquid exits a container.
//Note that this may be called quite often for the same container as liquid quickly moves in and out of it.
public override void OnExitContainer(BloodContainer container)
{
//for syringe liquids, this is usually empty.
}
}
}
3. Registering a custom liquid
Before any liquid can be used, it has to be registered to the liquid registry. More information here.
In short, add ModAPI.RegisterLiquid(DurabilitySyringe.DurabilitySerum.ID, new DurabilitySyringe.DurabilitySerum()); before anything in your Main method, like so:
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
ModAPI.RegisterLiquid(DurabilitySyringe.DurabilitySerum.ID, new DurabilitySyringe.DurabilitySerum());
...
4. Summary
In summary, this guide has shown how to:
Create a syringe spawnable
Create a syringe liquid and assign it to the syringe spawnable
Register a liquid
Ultimately, this is what our file looks like now:
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
ModAPI.RegisterLiquid(DurabilitySyringe.DurabilitySerum.ID, new DurabilitySyringe.DurabilitySerum());
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Acid Syringe"),
NameOverride = "Durability Syringe [mod name]",
DescriptionOverride = "Syringe full of an enhancing serum that makes limbs unbreakable.",
CategoryOverride = ModAPI.FindCategory("Biohazard"),
ThumbnailOverride = ModAPI.LoadSprite("durability syringe view.png"),
AfterSpawn = (Instance) =>
{
UnityEngine.Object.Destroy(Instance.GetComponent<SyringeBehaviour>());
Instance.GetOrAddComponent<DurabilitySyringe>();
}
}
);
}
}
public class DurabilitySyringe : SyringeBehaviour
{
public override string GetLiquidID() => DurabilitySerum.ID;
public class DurabilitySerum : Liquid
{
public const string ID = "DURABILITY SERUM [mod name]";
public DurabilitySerum()
{
Color = new UnityEngine.Color(0.69f, 1f, 0.35f);
}
public override void OnEnterLimb(LimbBehaviour limb)
{
limb.BreakingThreshold = 10000;
}
public override void OnUpdate(BloodContainer container) {}
public override void OnEnterContainer(BloodContainer container) {}
public override void OnExitContainer(BloodContainer container) {}
}
}
}

View File

@@ -0,0 +1,168 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomAttachment.html
Title: People Playground Modding - Creating a custom weapon attachment
==================================================
Creating a custom weapon attachment
by Aspa102
Weapon attachments are created similarly to syringes.
Contents1. Creating an attachment spawnable2. Creating an unique attachment3. Creating an attachment variant4. Summary
1. Creating an attachment spawnable
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
// registering a custom item
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Scope Attachment"), // derive from an attachment
NameOverride = "Bike Horn [mod name]",
DescriptionOverride = "A bike horn that goes on the top of a gun. Does what you think it does.",
CategoryOverride = ModAPI.FindCategory("Firearms"),
ThumbnailOverride = ModAPI.LoadSprite("horn view.png"),
AfterSpawn = (Instance) =>
{
// give the attachment a new sprite
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("bikehorn.png");
Instance.FixColliders();
// while you can use a new sound for the connection sound, you can do this to keep the current sound
// each attachment can have a unique connection sound
AudioClip attach = Instance.GetComponent<ScopeAttachmentBehaviour>().ConnectClip;
// destroy the existing attachment behaviour
UnityEngine.Object.Destroy(Instance.GetComponent<ScopeAttachmentBehaviour>());
// add the new attachment behaviour (unless it already exists)
var attachment = Instance.GetOrAddComponent<BikeHornAttachmentBehaviour>();
// set the connection sound
attachment.ConnectClip = attach;
// here is some other stuff you can change
attachment.AttachmentType = FirearmAttachmentType.AttachmentType.Scope; // whether the attachment will connect to the top or bottom of the gun
attachment.AttachmentOffset = Vector2.zero; // the offset from the attachment point (generally only used if you want the sprite to be within the gun and stuf
}
}
);
}
}
// define the attachment behaviour
public class BikeHornAttachmentBehaviour : FirearmAttachmentBehaviour
{
// method that is called on connection
public override void OnConnect() { }
// method that is called on disconnection
public override void OnDisconnect() { }
// method that is called when the gun is fired
public override void OnFire() { }
// method that is called when a bullet hits an object
public override void OnHit(BallisticsEmitter.CallbackParams args)
{
// args contains the position of the bullet, direction of the bullet, object that was hit, and the surface normal the bullet hit.
}
}
}
Most weapon attachments have a unique behaviour associated with them, exactly like how syringes do.
While this isn't strictly necessary, like how you can use the scope behaviour for any scopes, it allows for the creation of really unique and cool attachments.
2. Creating an unique attachment
The attachment we created in the above section doesn't actually do anything, so let's change that.
Instead of having the attachment do literally nothing, let's make it play a noise when the gun is fired.
public class BikeHornAttachmentBehaviour : FirearmAttachmentBehaviour
{
public AudioClip HornNoise; // The audio clip that will play on fire
public override void OnConnect() { }
public override void OnDisconnect() { }
public override void OnFire()
{
// on fire, get the physical behaviour and run the PlayClipOnce method
// note that you don't need to have the 'clip:' parts in the method's parameters, it makes it easier to tell what the parameters you're setting.
Phys.PlayClipOnce(clip: HornNoise, volume: 1.5f);
}
public override void OnHit(BallisticsEmitter.CallbackParams args) { }
}
Then we change the spawnable definition.
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Scope Attachment"),
NameOverride = "Bike Horn [mod name]",
DescriptionOverride = "A bike horn that goes on the top of a gun. Does what you think it does.",
CategoryOverride = ModAPI.FindCategory("Firearms"),
ThumbnailOverride = ModAPI.LoadSprite("horn view.png"),
AfterSpawn = (Instance) =>
{
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("bikehorn.png");
Instance.FixColliders();
AudioClip attach = Instance.GetComponent<ScopeAttachmentBehaviour>().ConnectClip;
UnityEngine.Object.Destroy(Instance.GetComponent<ScopeAttachmentBehaviour>());
var attachment = Instance.GetOrAddComponent<BikeHornAttachmentBehaviour>();
attachment.ConnectClip = attach;
attachment.AttachmentType = FirearmAttachmentType.AttachmentType.Scope;
attachment.AttachmentOffset = Vector2.zero;
// setting the new audio clip
attachment.HornNoise = ModAPI.LoadSound("bikehorn.wav");
}
}
);
Here is the final result:
3. Creating an attachment variant
The attachment above is completely unique and comes with their own functionality, but what if you want to make a variant of the scope attachment?
In order to do that, all you need to do is change the sprite and change some variables if you feel like it.
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Scope Attachment"), // derive from an attachment
NameOverride = "Hotdog Sight [mod name]",
DescriptionOverride = "A hotdog with a hole drilled down the middle. Improves accuracy by 100%.",
CategoryOverride = ModAPI.FindCategory("Firearms"),
ThumbnailOverride = ModAPI.LoadSprite("hog view.png"),
AfterSpawn = (Instance) =>
{
// give the attachment a new sprite
Instance.GetComponent<SpriteRenderer>().sprite = ModAPI.LoadSprite("hog.png");
Instance.FixColliders();
// we're changing the accuracy percentage of the attachment
// this should make the scope increase accuracy by 100%
Instance.GetComponent<ScopeAttachmentBehaviour>().AccuracyPercent = 100;
// change the connection sound if you feel like it
Instance.GetComponent<ScopeAttachmentBehaviour>().ConnectClip = ModAPI.LoadSound("hotdog.wav");
}
}
);
4. Summary
In summary, this guide has shown how to:
Create a new unique attachment
Create an attachment variant
A mod has been created with these items within it here, and has been open sourced here.

View File

@@ -0,0 +1,41 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCustomGun.html
Title: People Playground Modding - Creating a gun with a custom projectile
==================================================
Creating a gun with a custom projectile
by Talon
Creating a gun with a custom projectile
ModAPI.Register(
new Modification()
{
OriginalItem = ModAPI.FindSpawnable("Crossbow"), //Starting with the Crossbow sets up a nice template with everything we need.
NameOverride = "gay gun",
DescriptionOverride = "this gun is homosexual", //For more information, check https://en.wikipedia.org/wiki/Homosexuality for more documentation on the subject.
CategoryOverride = ModAPI.FindCategory("Firearms"),
ThumbnailOverride = ModAPI.LoadSprite("sprites/gay gun.png"),
AfterSpawn = (Instance) =>
{
var pLauncher = Instance.GetComponent<ProjectileLauncherBehaviour>();
pLauncher.projectileAsset = null;
var proj = new GameObject("Custom Projectile");
pLauncher.projectilePrefab = proj;
pLauncher.OnLaunch += (sender, projectile) =>
{
/*
* Within these brackets, 'projectile' is the GameObject that is created when the firearm is used, and can be treated like any other GameObject.
* For this example, we'll give it a sprite, collider, and behaviour.
* It's recommend that you cache the component on launch if you plan to assign values outside of the behaviour itself.
* (I don't know if there's any benefit to that, but it looks cleaner.)
*/
var c = projectile.AddComponent<CircleCollider2D>();
var sr = projectile.AddComponent<SpriteRenderer>();
sr.sprite = ModAPI.LoadSprite("gay projectile.png");
var newProj = projectile.AddComponent<GayProjectileBehaviour>();
};
}
});

View File

@@ -0,0 +1,31 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialCreatingMod.html
Title: People Playground Modding - Creating a mod
==================================================
Creating a mod
This guide will teach you the first step in creating a mod.
1. The mod folder
You first need to create a new folder inside the game's "Mods" folder. This folder is ideally named after your mod and is otherwise empty.
Inside your newly created folder, you need to create a mod.json file in order for the game to detect it. You can create one manually by looking at the metadata article, or you can generate one using the online generator.
An important detail here is that your author name and mod name should form a globally unique combination. Leave the scripts value as just script.cs for now, and leave the EntryPoint as Mod.Mod.
2. Script files
Create a script.cs file in the mod folder, as specified in the mod.json file.
Your mod folder should now contain two files: the mod.json file and a script.cs file.
A .cs file is a C# file. C# is the programming language People Playground and all its mods use. This is where you'll write all your code as well.
Because this is your only script file, it will contain the entry point. This is where the code lives that is first executed by the game. The entry point path is specified in the mod.json file, and should be Mod.Mod in your case.
Mod.Mod means "the Mod class , in the Mod namespace. This means your C# file should look like this:
using UnityEngine; //You'll probably need this...
namespace Mod
{
public class Mod
{
public static void Main()
{
//This method is the entry point.
}
}
}
That's it. This mod will show up in the mod list, but it doesn't do anything yet. If you know C#, read the API documentation.

View File

@@ -0,0 +1,63 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialMixing.html
Title: People Playground Modding - Custom liquid mixers
==================================================
Custom liquid mixers
You can add global liquid mixing instructions. Mixing instructions are responsible for turning source liquids into target liquids inside liquid containers. For example, mixing Mending serum and Acid to create Osteomorphisis agent.
Simple example
var mixer = new LiquidMixInstructions(
Liquid.GetLiquid(WaterBreathingSyringe.WaterBreathingSerum.ID), //source liquid A
Liquid.GetLiquid(Chemistry.Tritium.ID), //source liquid B
Liquid.GetLiquid(Chemistry.ExoticLiquid.ID)); //target liquid
LiquidMixingController.MixInstructions.Add(mixer); //add mixer to global list
Creating instructions
Preferrably done using one of the three constructors:
LiquidMixInstructions(Liquid[] sourceLiquids, Liquid targetLiquid, float ratePerSecond = 0.05f)
LiquidMixInstructions(Liquid sourceA, Liquid sourceB, Liquid targetLiquid, float ratePerSecond = 0.05f)
LiquidMixInstructions(Liquid sourceA, Liquid sourceB, Liquid sourceC, Liquid targetLiquid, float ratePerSecond = 0.05f)
Instructions have other members that you can edit.
var mixer = new LiquidMixInstructions(
Liquid.GetLiquid(WaterBreathingSyringe.WaterBreathingSerum.ID),
Liquid.GetLiquid(Chemistry.Tritium.ID),
Liquid.GetLiquid(Chemistry.ExoticLiquid.ID));
// Amount of liquid units that are converted every second
mixer.RatePerSecond = 0.1f;
// The optional container filter function. If this function returns false, the mixing will not take place
mixer.ContainerFilter = container => {
// this function will only return true if the container is a flask.
return container is FlaskBehaviour;
};
LiquidMixingController.MixInstructions.Add(mixer);
Restrictions
All liquids that are involved in a mixing process have to be registered in the Liquid registry. Here's a chapter on how to do that..
They can be registered before or after adding the mixer to the controller, it doesn't matter. Do note that all mixer instructions are cleared on map load. This means that your mixers should be added in your Main method.
Full example
using UnityEngine;
namespace Mod
{
public class Mod
{
public static void Main()
{
var bloodOilMixture = new LiquidMixInstructions(
Liquid.GetLiquid(Blood.ID),
Liquid.GetLiquid(Oil.ID),
Liquid.GetLiquid(Nitroglycerine.ID));
// i dont wan't liquids to mix inside a limb
bloodOilMixture.ContainerFilter = container => !(container is CirculationBehaviour);
LiquidMixingController.MixInstructions.Add(bloodOilMixture);
}
}
}

View File

@@ -0,0 +1,122 @@
URL: https://wiki.studiominus.nl/tutorials/tutorialSerialisation.html
Title: People Playground Modding - Saving, loading, copying, and pasting
==================================================
Saving, loading, copying, and pasting
This article is meant to teach modders how to make proper use of the serialisation system in People Playground. It should contain all the information necessary for players to be able to save/load/copy/paste your modded items. This article expects you to have a very basic understanding of C# (attributes, enums, and basic terms).
ContentsThe conceptWhat parts are saved?SkipSerialisation attributeOptout componentPrevent corruption, save in moderationResource management
The concept
In basic terms: serialisation is the process of storing an object on disk while deserialisation is the process of loading an object from disk.*
The game has a serialisation system that is specifically designed for People Playground spawnables. Naturally, only spawnables can be saved and loaded. The serialisation system is responsible for saving, loading, uploading, downloading, copying, and pasting contraptions from and to almost all versions of the game.
* (De)serialisation doesn't always go to/from the disk. Objects can be serialised to any storage device, including memory and the network.
What parts are saved?
The location, scale, and rotation of the transform and all its children and parents are saved.
Each component on each GameObject is also saved.
All fields and properties are saved, unless one of the following is true:*
The field/property is private
The field/property is marked as obsolete using the [System.Obsolete] attribute
The field/property is marked to be skipped using the [NonSerialized] attribute
The field/property is marked to be skipped using the [SkipSerialisation] attribute
The field is of a class type that is not a reference to another object in the scene
The property has no setter
The property has no getter
The property is of a class type
A GameObject can opt-out of being saved all together by having an Optout component attached to it.
* There are some more criteria / exceptions to this rule but they are not relevant to whatever you're doing, probably.
SkipSerialisation attribute
General rule of thumb: If you are writing a custom component, give the class the [SkipSerialisation] attribute.
If you are using any custom components, all fields need to be marked as [SkipSerialisation] unless it absolutely necessarily needs to be saved. Generally, a variable should be saved if its value changes while the player uses the item. For the vast majority of variables, this is not the case. Saving is actually entirely pointless for most components, in which case you should add the attribute to the class itself. Unnecessary saving will make your mod slow and could even cause contraptions to become corrupt. Save with caution!
For example, here are all the fields/properties of the LEDBulbBehaviour.
public class LEDBulbBehaviour : MonoBehaviour, Messages.IUse
{
[ColorUsage(false, true)]
public Color Color; // This field is saved!
[SkipSerialisation]
public SpriteRenderer LightSprite;
[SkipSerialisation]
public SpriteRenderer GlowSprite;
[SkipSerialisation]
public PhysicalBehaviour PhysicalBehaviour;
[SkipSerialisation]
public float MinBrightness = .2f;
[SkipSerialisation]
public float MaxCharge = 5;
public bool Activated; // This field is saved!
private float lastIntensity = float.MaxValue;
private bool colourCached = false;
Optout component
The Optout component is an important piece of the system. It prevents the entire GameObject from being saved, so it will be loaded with its default properties. Generally, this should be used for things like particle effects, lights, and all other strictly visual things.
Prevent corruption, save in moderation
When you add a custom component to a GameObject, that GameObject becomes dependent on your mod. When the player saves the object, this dependency is listed.
This also happens when you add your components to a vanilla spawnable. The stench of your mod is now all over the object and the game will remember this. If your custom component improperly interacts with the serialisation system, it's likely to render the entire contraption permanently unusable (corrupt).
If you are unsure of what to do with your fields and properties: mark them as SkipSerialisation.
If you are unsure of what to do with any child GameObjects you create, attach an Optout component.
Resource management
Loading textures and sounds is often the cause of serialisation issues in mods. 99% of the time, all resources should be loaded and ready to use within the Main method of your mod. It is almost always invalid to load resources inside a custom component.
If your mod has many assets that need to be used throughout your scripts, even outside the Main method, it's a good idea to set it up as follows:
// A structure containing static references to your assets.
public struct SpriteAssets
{
public static Sprite LowSignalNode;
public static Sprite HighSignalNode;
public static Sprite HighSignalGeneratorEnabled;
public static Sprite HighSignalGeneratorDisabled;
}
public static void Main()
{
// Load all these assets at the very start.
SpriteAssets.LowSignalNode = ModAPI.LoadSprite("Signal Node.png");
SpriteAssets.HighSignalNode = ModAPI.LoadSprite("HIGH Signal Node.png");
SpriteAssets.HighSignalGeneratorEnabled = ModAPI.LoadSprite("HIGH Signal Generator Enabled.png");
SpriteAssets.HighSignalGeneratorDisabled = ModAPI.LoadSprite("HIGH Signal Generator.png");
}
public class SignalNode : ORSignalGate
{
private SpriteRenderer spriteRenderer;
private Sprite highSignal, lowSignal;
protected override void Awake()
{
base.Awake();
spriteRenderer = GetComponent<SpriteRenderer>();
// Get the references from the structure
highSignal = SpriteAssets.HighSignalNode;
lowSignal = SpriteAssets.LowSignalNode;
spriteRenderer.sprite = lowSignal;
}
public override void ManagedUpdate()
{
base.ManagedUpdate();
var s = SignalStack > 0 ? highSignal : lowSignal;
// You can now change the sprite without a care in thw world.
if (spriteRenderer.sprite != s)
spriteRenderer.sprite = s;
}
}