Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
* <p>
* 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<RequestRespawn> 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.
* <p>
* Should be used to make the player ready (save the new reference, set inventory...).
*/
public static final StimulusEvent<Respawn> 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);
}
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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, float yaw, float 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() {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@
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;
import org.spongepowered.asm.mixin.Shadow;
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;
Expand Down Expand Up @@ -70,6 +74,28 @@ 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();
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*/);
}
}
}

return player.getRespawnTarget(alive, postDimensionTransition);
}

@Inject(
method = "respawnPlayer",
at = @At(
Expand All @@ -86,10 +112,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;
Expand Down