Services & Interfaces¶
The Mod Tools package declares 31 public interfaces, and they are not all the same kind of thing. This page sorts the ones that matter into three buckets — the ones you implement, the ones you ask the game for, and the ones you should leave alone.
Which bucket am I in?¶
| Bucket | What it means | Examples |
|---|---|---|
| 1. Implement | You write a component that implements the interface. Game systems find it with GetComponent / TryGetComponent and call into it. |
IUseable, IDestructible, IFlammable, IIgnitable, IErasable, ISaveableComponent |
| 2. Consume | Nothing in the Mod Tools implements it — the game does. You ask DependencyResolver for an instance and call it. |
IEnviroSkyManager, ICustomUIManager, IEntityDefinitionDatabase, IDestructionObjectPool |
| 3. Leave alone | Declared in the package, but with zero implementers and zero consumers — or explicitly obsolete. | IPoolable, IHaveFusetime, IFuseConnectionMetadata, IExtinguishable, IShakeable |
Everything below lives in the FireworksMania.Core assembly, which is auto-referenced — a script in your mod can using it with no setup at all. See Setting Up Scripts in a Mod.
1. Implement these on your own components¶
| Interface | Namespace | Use this when… | Members |
|---|---|---|---|
IUseable |
FireworksMania.Core.Behaviors |
The player's Use key should do something on your object, with the interaction prompt and highlight | BeginUse(), EndUse(), IsInUse, ShowHighlight, ShowInteractionUI, CustomText, GameObject |
IDestructible |
FireworksMania.Core.Behaviors |
Your object should take explosion damage and break | ApplyDamage(float), IsDestroyed |
IFlammable |
FireworksMania.Core.Behaviors |
Your object should react to being near an explosion — catch fire, char, ignite an effect | ApplyFireForce(float) |
IIgnitable |
FireworksMania.Core.Behaviors.Fireworks.Parts |
Your object should be lightable by a torch or an explosion, but is not a firework | IgnitePositionTransform, Ignite(float), IgniteInstant(), Enabled, IsIgnited |
IErasable |
FireworksMania.Core.Behaviors |
You want to override what the in-game Eraser tool does to your object | Erase() |
ISaveableComponent |
FireworksMania.Core.Persistence |
Your object carries state that should survive a blueprint save | CaptureState(), RestoreState(CustomEntityComponentData), SaveableComponentTypeId |
ISaveableComponent has a page of its own — see Saving & Loading (Blueprints).
Shipped components you can use instead of writing code¶
| Interface | Component | Menu |
|---|---|---|
IUseable |
UseableBehavior (local, not synced) |
Fireworks Mania/Behaviors/Other/UseableBehavior |
IUseable |
UseableNetworkBehavior (server-authoritative) |
Fireworks Mania/Behaviors/Other/UseableNetworkBehavior |
IErasable |
ErasableBehavior |
Fireworks Mania/Behaviors/Other/ErasableBehavior |
IDestructible |
DestructibleBehavior |
no [AddComponentMenu] — it shows up under Unity's default Scripts category |
Both IUseable components expose the same Inspector surface — Custom Text, Show Highlight, Show Interaction UI, and OnBeginUse / OnEndUse UnityEvents — so a lot of "usable object" work is zero code. ErasableBehavior is added automatically to every firework deriving from BaseFireworkBehavior: OnValidate adds one in the Editor, and Awake() adds one at runtime if the GameObject still has no IErasable.
A custom IErasable does not stop the Editor from adding ErasableBehavior
The runtime check in BaseFireworkBehavior.Awake() skips the add when the GameObject already has any IErasable. The Editor check in OnValidate looks for the concrete ErasableBehavior only, so it adds one anyway and you end up with two IErasable components on the prefab. If you are replacing the eraser behaviour, delete the auto-added ErasableBehavior from the prefab by hand.
IUseable is easy to miss
It does not have its own file. IUseable is declared at the top of Behaviors/UseableBehavior.cs, which is why searching the project for IUseable.cs comes up empty.
The lookup rule that trips everyone up¶
Explosions do not search your hierarchy. They query the exact GameObject they hit, and which GameObject that is depends on the interface:
| Interface | Found on | Consequence |
|---|---|---|
IDestructible |
the Collider's GameObject | A destructible on the parent of a collider is never found |
IFlammable |
the Collider's GameObject | Same |
IIgnitable |
the Rigidbody's GameObject | Must sit next to the Rigidbody, not next to the collider |
Colliders on children
The common setup — a root with the Rigidbody and a child with the mesh and its collider — puts these on two different GameObjects. Put your IDestructible / IFlammable on the child that has the collider, and your IIgnitable on the root that has the rigidbody. One component that implements all three only works if the collider and rigidbody happen to be on the same GameObject.
Sample — a light switch the player can use¶
using FireworksMania.Core.Behaviors;
using UnityEngine;
namespace YourNick.MyMod
{
[AddComponentMenu("My Mod/Light Switch")]
public class LightSwitch : MonoBehaviour, IUseable
{
[SerializeField]
[Tooltip("Light toggled when the player uses this object")]
private Light _light;
public void BeginUse()
{
IsInUse = true;
if (_light != null)
_light.enabled = !_light.enabled;
}
public void EndUse() => IsInUse = false;
public bool IsInUse { get; private set; }
public bool ShowHighlight => true;
public bool ShowInteractionUI => true;
public string CustomText => _light != null && _light.enabled ? "Turn off" : "Turn on";
public GameObject GameObject => this.gameObject;
}
}
This is deliberately local — the light flips only on the peer that pressed Use. For a switch every player sees, back the state with a NetworkVariable<bool> written on the server, or just use the shipped UseableNetworkBehavior and wire its UnityEvents. See Multiplayer & Netcode.
Sample — a crate that explosions can break¶
using FireworksMania.Core.Behaviors;
using UnityEngine;
namespace YourNick.MyMod
{
[AddComponentMenu("My Mod/Breakable Crate")]
public class BreakableCrate : MonoBehaviour, IDestructible
{
[SerializeField] private float _hitPoints = 100f;
[SerializeField] private GameObject _brokenVfx;
public bool IsDestroyed { get; private set; }
public void ApplyDamage(float damage)
{
if (IsDestroyed)
return;
_hitPoints -= damage;
if (_hitPoints > 0f)
return;
IsDestroyed = true;
if (_brokenVfx != null)
Instantiate(_brokenVfx, transform.position, transform.rotation);
}
}
}
Damage is just a number
ApplyDamage gets the damage amount and nothing else — no position, no range, no force. If you need to know where the explosion was, listen for MessengerEventApplyExplosionForce on The Messenger Event Bus, which carries Position, Range and ActualExplosionForce.
2. Consume these from the game via DependencyResolver¶
These interfaces have no implementation in the Mod Tools package. The game provides them at runtime; your job is to ask for them.
| Interface | Namespace | Use this when… | Members |
|---|---|---|---|
IEnviroSkyManager |
FireworksMania.Core.Common |
Something in your mod reacts to day/night — street lights, neon signs | IsNight |
ICustomUIManager |
FireworksMania.Core.Common |
Your mod has a world-space Canvas that must be re-parented into the player's UI when opened |
ShowCanvas(Canvas), HideCanvas(Canvas), RegisterCanvas(Canvas, Transform), UnregisterCanvas(Canvas) |
IEntityDefinitionDatabase |
FireworksMania.Core.Definitions |
You need to resolve another entity — vanilla or modded — by its definition id at runtime | GetEntityDefinition(string) |
IDestructionObjectPool |
FireworksMania.Core.Behaviors |
Almost never. This is internal plumbing for spawning destruction debris | GetNetworkObject(GameObject, Vector3, Quaternion) |
IInputManager is resolvable, but empty
FireworksMania.Core.Common.IInputManager is declared with no members. The game implements it and FiringSystemControllerBehavior resolves it, but there is nothing on it for your code to call, so resolving it yourself buys you nothing.
DependencyResolver¶
Namespace: FireworksMania.Core
Base Class: MonoBehaviour
API: public static DependencyResolver Instance { get; } and public T Get<T>()
What Get<T>() actually does, in order:
- Looks in an internal cache keyed by
typeof(T).FullName, dropping the entry if the cached Unity object has since been destroyed. - On a miss, runs
FindObjectsByType<MonoBehaviour>(FindObjectsInactive.Exclude, …)and takes the first one that implementsT. - Returns
default— i.e.null— if nothing matches.
Three consequences follow directly from that:
| Behaviour | What it means for you |
|---|---|
FindObjectsInactive.Exclude |
Only active MonoBehaviours in loaded scenes are found. A disabled manager is invisible. |
Returns default on failure |
It never throws and never logs. A missing service looks exactly like a working one that returned null. |
Instance is a static set in Awake |
It is always null inside the Mod Tools Unity project — nothing there registers a resolver. |
Always null-check, twice
DependencyResolver.Instance can be null (no resolver in the scene) and Get<T>() can return null (no implementation active). Write DependencyResolver.Instance?.Get<T>() and then check the result. Any code path that assumes a service exists will throw a NullReferenceException the first time you press Play in the Mod Tools project — where the answer is guaranteed to be null.
Sample — resolve a service with a sane fallback¶
using FireworksMania.Core;
using FireworksMania.Core.Common;
using UnityEngine;
namespace YourNick.MyMod
{
[AddComponentMenu("My Mod/Night Light")]
public class NightLight : MonoBehaviour
{
[SerializeField]
[Tooltip("Light that is only on after dark")]
private Light _light;
private void Start()
{
//Instance is null in the Mod Tools project, and Get<T> returns null if nothing active implements it
var sky = DependencyResolver.Instance?.Get<IEnviroSkyManager>();
//Fallback: with no sky manager, behave as if it is daytime rather than throwing
var isNight = sky != null && sky.IsNight;
if (_light != null)
_light.enabled = isNight;
}
}
}
For day/night specifically, you may not need code at all
The package ships DayNightCycleTriggerBehavior (Fireworks Mania/Behaviors/Other/DayNightCycleTriggerBehavior) with OnDayActions and OnNightActions UnityEvents and a Random Delay In Seconds field, so a street lamp can be wired entirely in the Inspector.
DestructibleBehavior needs the game running
The shipped DestructibleBehavior calls DependencyResolver.Instance.Get<IDestructionObjectPool>() without a null check when it spawns its destroyed prefab, and only does anything at all on the server. Testing destruction inside the Mod Tools project will not work — build the mod and test it in the game.
3. Declared but unused — do not implement¶
These compile, and they look like extension points. They are not. Implementing one costs you nothing but achieves nothing.
| Interface | Namespace | Status |
|---|---|---|
IPoolable |
FireworksMania.Core.Behaviors |
Zero implementers, zero consumers in the package |
IHaveFusetime |
FireworksMania.Core.Behaviors.Fireworks.Parts |
Zero implementers, zero consumers. Fuse exposes FuseTime through IFuse instead |
IFuseConnectionMetadata |
FireworksMania.Core.Behaviors.Fireworks.Parts |
Zero implementers, zero consumers |
IExtinguishable |
FireworksMania.Core.Behaviors.Fireworks.Parts |
Zero implementers, zero consumers. Fuse has a private Extinguish() but does not implement the interface |
IShakeable |
FireworksMania.Core.Behaviors |
[Obsolete] — "As we now use CinemachineShake and MessengerEventApplyShakeEffect instead" |
IHaveObjectInfo |
FireworksMania.Core.Interactions |
[Obsolete(…, error: true)] — referencing it is a compile error. Use IAmGameObject |
Camera shake replaced IShakeable
Explosions now broadcast MessengerEventApplyShakeEffect on the Messenger bus instead of calling into an IShakeable. If you want to shake the camera, or react to something else shaking it, go through The Messenger Event Bus.
Why they still exist
They are either leftovers, or hooks whose consuming systems live in the full game rather than in the Mod Tools. Either way, nothing in the Mod Tools will ever call them, and nothing in the package shows that the game will. Do not build a feature on one.
Worth knowing about¶
Not in any of the three buckets, but they come up:
| Type | Namespace | What it is |
|---|---|---|
IAmGameObject |
FireworksMania.Core.Interactions |
Implement it (Name, GameObject) to give your object a friendly display name in the game's UI |
IsPickedUp |
FireworksMania.Core.Interactions |
Despite the name, a marker MonoBehaviour, added and removed at runtime by the pickup system. TryGetComponent<IsPickedUp>(out _) to test "the player is holding this". Never add it in the Editor |
IHaveBaseEntityDefinition |
FireworksMania.Core.Persistence |
BaseEntityDefinition EntityDefinition { get; set; } — implemented by SaveableEntity and every firework root, so a component can find out which definition it belongs to |
Where to go next¶
| Page | Why |
|---|---|
| Saving & Loading (Blueprints) | ISaveableComponent in full, with its traps |
| Multiplayer & Netcode | Which of these run server-only, and how to make your own state authoritative |
| The Messenger Event Bus | The other way game systems talk to mods |
| Behaviors | The shipped components that already implement these interfaces |
| Firework Parts | Fuse, FuseConnectionPoint and the rest of the fuse interfaces |