Server Synchronization
The consuming mod owns gameplay decisions; Nebulon owns effect serialization, spawn/update/remove packets, late-join synchronization, dimension scoping, and client cleanup on disconnect.
Only players with a compatible Nebulon networking channel receive packets — a client without the library is simply skipped, never disconnected.
Persistent effects survive handle updates, but persistence across server restarts is not implicit. Recreate them from your mod's own saved state.
Synchronized effects
NebulonNetworking.syncedEffects() mirrors the client-local service, but on ServerWorld:
EffectHandle<MagicCircle> handle = NebulonNetworking.syncedEffects()
.spawn(serverWorld, circle, 20 * 30);
handle.update(current -> /* new description */);
handle.remove();
public interface SyncedEffectService {
<E extends WorldEffect> EffectHandle<E> spawn(ServerWorld world, E effect);
<E extends WorldEffect> EffectHandle<E> spawn(ServerWorld world, E effect, int lifetimeTicks);
void clear(ServerWorld world);
int size();
}
All built-in effect types ship with codecs and work out of the box. A custom effect type needs a PacketCodec registered with SyncedEffectRegistry — see Custom effects.
Synchronized particles
Individual particles are never sent over the network. Instead, ParticleEmission represents one complete semantic event; each client expands it locally from the same seed:
ParticleEmission burst = ParticleEmission.burst(
wisp,
ParticleSpawn.at(position),
64,
Vec3dRange.ofEqual(0.3, 0.2, 0.3), // position spread
Vec3dRange.ofEqual(0.04, 0.03, 0.04), // velocity spread
seed);
NebulonNetworking.syncedParticles().emit(serverWorld, burst);
Emission factories match the client-side spawn patterns: single, burst, circle, sphere, line, and cone — each remains one packet regardless of particle count. Vec3dRange expresses symmetric (ofEqual) or explicit min/max spreads.
The world key travels with every event, so a packet delayed across a dimension change is discarded instead of appearing in the wrong world.
Persistent emitters
An emitter repeats an immutable emission on client ticks. Its server handle follows the same lifecycle model as a synchronized effect:
ParticleEmitter emitter = ParticleEmitter.builder(burst)
.initialDelay(5) // ticks before the first emission
.interval(10) // ticks between emissions
.seedStep(31) // seed shift per emission
.build();
ParticleEmitterHandle handle = NebulonNetworking.syncedParticles()
.spawnEmitter(serverWorld, emitter, 20 * 30);
handle.update(current -> current.withEmission(updatedBurst));
handle.remove();
Emitters support server-side expiry, thread-safe replacement, explicit removal, dimension scoping, disconnect cleanup, and late-join sync. Late joiners receive only currently active emitters with their current age and remaining lifetime — historical one-shot emissions are not replayed.
seedStep shifts the random pattern's seed each emission while keeping the sequence deterministic across all clients; 0 repeats the identical distribution every time.
public interface SyncedParticleService {
void emit(ServerWorld world, ParticleEmission emission);
ParticleEmitterHandle spawnEmitter(ServerWorld world, ParticleEmitter emitter);
ParticleEmitterHandle spawnEmitter(ServerWorld world, ParticleEmitter emitter, int lifetimeTicks);
void clearParticles(ServerWorld world);
void clearEmitters(ServerWorld world);
int emitterCount();
}
Targeting specific players
For a selected recipient set rather than the whole dimension, NebulonServerParticles is a recipient-scoped facade over the same semantic protocol:
NebulonServerParticles.forPlayer(player).burst(...);
NebulonServerParticles.forPlayers(players)...;
NebulonServerParticles.forAllPlayers(serverWorld)...;
NebulonServerParticles.forPlayersAround(world, box, exceptPlayer)...;