Do not copy NightCoop's .reds files
The files NightCoopSdk.reds and NightCoopSdkNative.reds ship inside NightCoop's plugin folder
(red4ext/plugins/NightCoop/redscript/). Do not copy them into your mod. Two definitions of the same class break the
compilation of every script. They are in the SDK folder for reading only.
What they declare:
- module
NightCoop: the enums NightCoopSdkResult, NightCoopSdkModFlag, NightCoopSdkEventKind, NightCoopSdkSessionEvent,
NightCoopSdkPoseBit;
- at top level: the native class
NightCoopSdk and the scripted class NightCoopSdkEvent (NightCoop creates its instances).
Make the mod compile without NightCoop
Without NightCoop (not installed, or not loaded) the module NightCoop does not exist,
@if(ModuleExists("NightCoop")) is false, and the code under it is not compiled. The game starts normally. So guard
every piece of code that touches NightCoop, and give it a stub:
@if(ModuleExists("NightCoop"))
import NightCoop.*
@if(ModuleExists("NightCoop"))
private func CoopSend(text: String) -> Void {
NightCoopSdk.Send("yourname.yourmod", -2, 1, text, false);
}
@if(!ModuleExists("NightCoop"))
private func CoopSend(text: String) -> Void {}
import NightCoop.* brings the enums. Flags and events are enum values; convert with EnumInt(...).
Register
Register once per load, in a ScriptableSystem's OnAttach. The call runs again on every game load, which is safe: with the
same id it updates name, version, flags and protocol and returns true. If the same id is registered by a C plugin, scripts
get false.
@if(ModuleExists("NightCoop"))
public class MyCoopSystem extends ScriptableSystem {
private func OnAttach() -> Void {
if NightCoopSdk.Version() == 0 {
return; // 0: the player switched the SDK off
}
// Declare every use: an undeclared one answers Denied (sdk/SECURITY.md)
let flags: Int32 = EnumInt(NightCoopSdkModFlag.NeededByAll) | EnumInt(NightCoopSdkModFlag.UsesChannels);
if NightCoopSdk.RegisterMod("yourname.yourmod", "Your Mod", "1.0.0", 1, flags) {
NightCoopSdk.Listen("yourname.yourmod", this, n"OnCoopEvent");
};
}
protected cb func OnCoopEvent(evt: ref<NightCoopSdkEvent>) -> Void {
if evt.kind == EnumInt(NightCoopSdkEventKind.State) && Equals(evt.key, "count") {
// evt.data is the new value as text
};
}
}
@if(!ModuleExists("NightCoop"))
public class MyCoopSystem extends ScriptableSystem {
}
Arguments of RegisterMod: id ([a-z0-9._-], 3..64 bytes, stable forever), display name, version string, protocol
(your own wire version) and flags (a bit set: EnumInt(A) | EnumInt(B), see api.md).
Receive events: Listen or PollEvent
Listen(id, target, function) makes NightCoop call target.function(event) on the game thread, once per event, from its
frame, instead of queueing the events.
- The function must take exactly one parameter,
ref<NightCoopSdkEvent>. Listen returns false otherwise.
- Mark it
cb func and give the name as a CName: n"OnCoopEvent".
- The target is held weakly. When it dies (the world unloads), events go back to the queue. Call
Listen again on the next
OnAttach, as the example does.
NightCoopSdk.Listen(id, null, n"") stops the callback.
ListenFrame(id, target, function) makes NightCoop call target.function(dt: Float) every frame while the player is
in a world — the place for work your mod does each frame (look at what the game does, feed visuals from a partner's stream).
A chain of DelaySystem.DelayCallbackNextFrame stops when the game loads a save; this keeps going. Call it from OnAttach
next to Listen.
PollEvent(id) returns the next queued event, or null. Use it if you prefer to pull, for example from your own update
loop. The queue holds up to 1024 events per mod; the oldest drop.
Fields of NightCoopSdkEvent:
| Field |
Meaning |
kind |
NightCoopSdkEventKind: Session 1, Message 2, State 3, EntityState 4, PartnerPose 5, SharedSpawn 6 |
peer |
Session: the peer it is about. Message: the sender. State: who set it. EntityState: the owner |
a |
Session: NightCoopSdkSessionEvent. Message: the channel. EntityState: the key. SharedSpawn: 1 appeared / 0 gone |
key |
Shared-state key (State events) |
removed |
The state key or the entity-state key was removed or cleared |
data |
The payload as text. Empty if it is not valid UTF-8 |
bytes |
The payload as array<Uint8>, always |
entity |
EntityState, SharedSpawn: our local mirror/copy (EntityID) |
ref |
The network reference, packed in a Uint64 (kind 8, owner 24, id 32 bits) |
poseValid, head, leftHand, rightHand, aimOrigin, aimDir |
PartnerPose; poseValid 0 means the source is gone |
Calling the API
NightCoopSdk.Send("yourname.yourmod", -2, 1, "hello", false); // to everyone who has the mod, unreliable
NightCoopSdk.SetState("yourname.yourmod", "count", "7"); // host: set and spread; guest: ask the host
let count: String = NightCoopSdk.GetState("yourname.yourmod", "count");
Every function takes your mod id as its first argument. Peers are Int32: the host is 0, "everyone" is -2, "none" is
-1. Int32 results are 0 or a positive count on success and a negative NightCoopSdkResult on failure. All functions
are listed in api.md.
This page is docs/getting-started-redscript.md of the SDK.