From cc8f14c1f66031c0cc240959142ddf9985a75a38 Mon Sep 17 00:00:00 2001
From: HungryMuttis <88585435+HungryMuttis@users.noreply.github.com>
Date: Thu, 19 Feb 2026 23:33:16 +0200
Subject: [PATCH 1/3] Added respawning.
---
.../api/game/event/GamePlayerEvents.java | 45 +++++++++++++++++++
.../api/game/player/RespawnResult.java | 37 +++++++++++++++
.../impl/game/manager/ManagedGameSpace.java | 5 +++
.../game/manager/ManagedGameSpacePlayers.java | 18 ++++++++
.../mixin/game/space/PlayerManagerMixin.java | 35 +++++++++++++--
5 files changed, 137 insertions(+), 3 deletions(-)
create mode 100644 src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java b/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java
index b6c54df42..84956e3cb 100644
--- a/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java
+++ b/src/main/java/xyz/nucleoid/plasmid/api/game/event/GamePlayerEvents.java
@@ -8,6 +8,7 @@
import xyz.nucleoid.plasmid.api.game.player.JoinAcceptorResult;
import xyz.nucleoid.plasmid.api.game.player.JoinOffer;
import xyz.nucleoid.plasmid.api.game.player.JoinOfferResult;
+import xyz.nucleoid.plasmid.api.game.player.RespawnResult;
import xyz.nucleoid.plasmid.api.game.GameActivity;
import xyz.nucleoid.plasmid.api.game.GameTexts;
import xyz.nucleoid.stimuli.event.StimulusEvent;
@@ -200,6 +201,42 @@ public final class GamePlayerEvents {
}
});
+ /**
+ * Called when a player is about to respawn.
+ * This event is invoked before the new {@link ServerPlayerEntity} is created.
+ *
+ * Listeners should return {@link RespawnResult.Respawn} with the target world to keep the player in the game.
+ * Otherwise, the player will be thrown out of the game.
+ */
+ public static final StimulusEvent REQUEST_RESPAWN = StimulusEvent.create(RequestRespawn.class, ctx -> (gameSpace, player) -> {
+ try {
+ for (var listener : ctx.getListeners()) {
+ var result = listener.onRequestRespawn(gameSpace, player);
+ if (!(result instanceof RespawnResult.Pass)) {
+ return result;
+ }
+ }
+ } catch (Throwable throwable) {
+ ctx.handleException(throwable);
+ }
+ return RespawnResult.PASS;
+ });
+
+ /**
+ * Called after the new player entity has been created and assigned to the correct world.
+ *
+ * Should be used to make the player ready (save the new reference, set inventory...).
+ */
+ public static final StimulusEvent RESPAWN = StimulusEvent.create(Respawn.class, ctx -> (oldPlayer, respawnedPlayer, alive) -> {
+ try {
+ for (var listener : ctx.getListeners()) {
+ listener.onRespawn(oldPlayer, respawnedPlayer, alive);
+ }
+ } catch (Throwable throwable) {
+ ctx.handleException(throwable);
+ }
+ });
+
public interface Add {
void onAddPlayer(ServerPlayerEntity player);
}
@@ -229,4 +266,12 @@ public interface LeaveMessage {
@Nullable
Text onLeaveMessageCreation(ServerPlayerEntity player, @Nullable Text currentText, Text defaultText);
}
+
+ public interface RequestRespawn {
+ RespawnResult onRequestRespawn(GameSpace gameSpace, ServerPlayerEntity player);
+ }
+
+ public interface Respawn {
+ void onRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity respawnedPlayer, boolean alive);
+ }
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java b/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
new file mode 100644
index 000000000..9987bb086
--- /dev/null
+++ b/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
@@ -0,0 +1,37 @@
+package xyz.nucleoid.plasmid.api.game.player;
+
+import net.minecraft.server.world.ServerWorld;
+import net.minecraft.util.math.Vec3d;
+import net.minecraft.world.TeleportTarget;
+
+public sealed interface RespawnResult permits RespawnResult.Pass, RespawnResult.Respawn, RespawnResult.Leave {
+ Pass PASS = new Pass();
+ Leave LEAVE = new Leave();
+
+ final class Pass implements RespawnResult {
+ private Pass() {
+ }
+ }
+
+ non-sealed interface Respawn extends RespawnResult {
+ TeleportTarget target();
+
+ static Respawn at(ServerWorld world, Vec3d pos) {
+ return at(world, pos, new Vec3d(0, 0, 0));
+ }
+ static Respawn at(ServerWorld world, Vec3d pos, Vec3d velocity) {
+ return at(world, pos, velocity, 0, 0);
+ }
+ static Respawn at(ServerWorld world, Vec3d pos, Vec3d velocity, int yaw, int pitch) {
+ return at(new TeleportTarget(world, pos, velocity, yaw, pitch, TeleportTarget.NO_OP));
+ }
+ static Respawn at(TeleportTarget target) {
+ return () -> target;
+ }
+ }
+
+ final class Leave implements RespawnResult {
+ private Leave() {
+ }
+ }
+}
diff --git a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java
index c551714b8..01d2a6b04 100644
--- a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java
+++ b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpace.java
@@ -257,6 +257,11 @@ void onPlayerRemove(ServerPlayerEntity player) {
}
}
+ void onPlayerRespawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity respawnedPlayer) {
+ this.manager.removePlayerFromGameSpace(this, oldPlayer);
+ this.manager.addPlayerToGameSpace(this, respawnedPlayer);
+ }
+
void onAddWorld(RuntimeWorldHandle worldHandle) {
this.manager.addDimensionToGameSpace(this, worldHandle.asWorld().getRegistryKey());
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpacePlayers.java b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpacePlayers.java
index 6d88c581e..cf78f6f45 100644
--- a/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpacePlayers.java
+++ b/src/main/java/xyz/nucleoid/plasmid/impl/game/manager/ManagedGameSpacePlayers.java
@@ -155,6 +155,24 @@ public boolean remove(ServerPlayerEntity player) {
return true;
}
+ public void respawn(ServerPlayerEntity oldPlayer, ServerPlayerEntity respawnedPlayer) {
+ if (!this.set.contains(oldPlayer)) {
+ return;
+ }
+
+ this.set.remove(oldPlayer);
+ this.set.add(respawnedPlayer);
+
+ if (this.players.remove(oldPlayer)) {
+ this.players.add(respawnedPlayer);
+ }
+ if (this.spectators.remove(oldPlayer)) {
+ this.spectators.add(respawnedPlayer);
+ }
+
+ this.space.onPlayerRespawn(oldPlayer, respawnedPlayer);
+ }
+
void clear() {
this.set.clear();
}
diff --git a/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java b/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
index 62a995f22..e97f76c03 100644
--- a/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
+++ b/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
@@ -27,9 +27,12 @@
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+import xyz.nucleoid.plasmid.api.game.event.GamePlayerEvents;
+import xyz.nucleoid.plasmid.api.game.player.RespawnResult;
import xyz.nucleoid.plasmid.impl.game.manager.GameSpaceManagerImpl;
import xyz.nucleoid.plasmid.impl.player.isolation.PlayerManagerAccess;
import xyz.nucleoid.plasmid.impl.player.isolation.PlayerResetter;
@@ -70,6 +73,26 @@ private void removePlayer(ServerPlayerEntity player, CallbackInfo ci) {
}
}
+ @Redirect(
+ method = "respawnPlayer",
+ at = @At(
+ value = "INVOKE",
+ target = "Lnet/minecraft/server/network/ServerPlayerEntity;getRespawnTarget(ZLnet/minecraft/world/TeleportTarget$PostDimensionTransition;)Lnet/minecraft/world/TeleportTarget;"
+ )
+ )
+ private TeleportTarget respawnPlayer(ServerPlayerEntity player, boolean alive, TeleportTarget.PostDimensionTransition postDimensionTransition) {
+ var gameSpace = GameSpaceManagerImpl.get().byPlayer(player);
+ if (gameSpace != null) {
+ RespawnResult result = gameSpace.getBehavior().invoker(GamePlayerEvents.REQUEST_RESPAWN).onRequestRespawn(gameSpace, player);
+ if (result instanceof RespawnResult.Respawn respawn) {
+ TeleportTarget target = respawn.target();
+ return new TeleportTarget(target.world(), target.position(), target.velocity(), target.yaw(), target.pitch(), target.missingRespawnBlock(), false /*asPassenger() is ambiguous*/, target.relatives(), TeleportTarget.SEND_TRAVEL_THROUGH_PORTAL_PACKET /*mark*/);
+ }
+ }
+
+ return player.getRespawnTarget(alive, postDimensionTransition);
+ }
+
@Inject(
method = "respawnPlayer",
at = @At(
@@ -86,10 +109,16 @@ private void respawnPlayer(
var gameSpace = GameSpaceManagerImpl.get().byPlayer(oldPlayer);
if (gameSpace != null) {
- gameSpace.getPlayers().remove(oldPlayer);
+ if (respawnTarget.postTeleportTransition() == TeleportTarget.SEND_TRAVEL_THROUGH_PORTAL_PACKET) {
+ gameSpace.getPlayers().respawn(oldPlayer, respawnedPlayer);
+
+ gameSpace.getBehavior().invoker(GamePlayerEvents.RESPAWN).onRespawn(oldPlayer, respawnedPlayer, alive);
+ } else {
+ gameSpace.getPlayers().remove(oldPlayer);
- this.plasmid$loadIntoPlayer(respawnedPlayer);
- respawnedPlayer.setServerWorld(respawnWorld);
+ this.plasmid$loadIntoPlayer(respawnedPlayer);
+ respawnedPlayer.setServerWorld(respawnWorld);
+ }
// this is later used to apply back to the respawned player, and we want to maintain that
var interactionManager = respawnedPlayer.interactionManager;
From ac204042b8ca2997461c5509f45d4319480efbc7 Mon Sep 17 00:00:00 2001
From: HungryMuttis <88585435+HungryMuttis@users.noreply.github.com>
Date: Fri, 20 Feb 2026 18:33:38 +0200
Subject: [PATCH 2/3] Fixed ambiguity.
---
.../plasmid/mixin/game/space/PlayerManagerMixin.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java b/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
index e97f76c03..6c31a005c 100644
--- a/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
+++ b/src/main/java/xyz/nucleoid/plasmid/mixin/game/space/PlayerManagerMixin.java
@@ -20,6 +20,7 @@
import net.minecraft.world.TeleportTarget;
import net.minecraft.world.World;
import net.minecraft.world.dimension.DimensionType;
+import org.lwjgl.opengl.NVVertexArrayRange;
import org.slf4j.Logger;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
@@ -86,7 +87,9 @@ private TeleportTarget respawnPlayer(ServerPlayerEntity player, boolean alive, T
RespawnResult result = gameSpace.getBehavior().invoker(GamePlayerEvents.REQUEST_RESPAWN).onRequestRespawn(gameSpace, player);
if (result instanceof RespawnResult.Respawn respawn) {
TeleportTarget target = respawn.target();
- return new TeleportTarget(target.world(), target.position(), target.velocity(), target.yaw(), target.pitch(), target.missingRespawnBlock(), false /*asPassenger() is ambiguous*/, target.relatives(), TeleportTarget.SEND_TRAVEL_THROUGH_PORTAL_PACKET /*mark*/);
+ if (target instanceof TeleportTarget(var world, var pos, var vel, var yaw, var pitch, var missing, var asPassenger, var rel, var post)) {
+ return new TeleportTarget(world, pos, vel, yaw, pitch, missing, asPassenger, rel, TeleportTarget.SEND_TRAVEL_THROUGH_PORTAL_PACKET /*mark*/);
+ }
}
}
From cc2e0d3d45055b0ec6b21248a6491b92811c81dd Mon Sep 17 00:00:00 2001
From: HungryMuttis <88585435+HungryMuttis@users.noreply.github.com>
Date: Fri, 20 Feb 2026 18:34:16 +0200
Subject: [PATCH 3/3] Fixed type mismatch.
---
.../xyz/nucleoid/plasmid/api/game/player/RespawnResult.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java b/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
index 9987bb086..e4b2978d6 100644
--- a/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
+++ b/src/main/java/xyz/nucleoid/plasmid/api/game/player/RespawnResult.java
@@ -22,7 +22,7 @@ static Respawn at(ServerWorld world, Vec3d pos) {
static Respawn at(ServerWorld world, Vec3d pos, Vec3d velocity) {
return at(world, pos, velocity, 0, 0);
}
- static Respawn at(ServerWorld world, Vec3d pos, Vec3d velocity, int yaw, int pitch) {
+ static Respawn at(ServerWorld world, Vec3d pos, Vec3d velocity, float yaw, float pitch) {
return at(new TeleportTarget(world, pos, velocity, yaw, pitch, TeleportTarget.NO_OP));
}
static Respawn at(TeleportTarget target) {