Skip to content

Commit

Permalink
Add join accept event and modify offer event
Browse files Browse the repository at this point in the history
  • Loading branch information
pufmat authored and Patbox committed Nov 5, 2024
1 parent 5e1ef9e commit a983800
Show file tree
Hide file tree
Showing 13 changed files with 350 additions and 131 deletions.
50 changes: 36 additions & 14 deletions src/main/java/xyz/nucleoid/plasmid/game/event/GamePlayerEvents.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
package xyz.nucleoid.plasmid.game.event;

import com.mojang.authlib.GameProfile;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.Vec3d;
import xyz.nucleoid.plasmid.game.GameActivity;
import xyz.nucleoid.plasmid.game.GameResult;
import xyz.nucleoid.plasmid.game.GameSpace;
import xyz.nucleoid.plasmid.game.GameTexts;
import xyz.nucleoid.plasmid.game.player.JoinIntent;
import xyz.nucleoid.plasmid.game.player.JoinAcceptor;
import xyz.nucleoid.plasmid.game.player.JoinAcceptorResult;
import xyz.nucleoid.plasmid.game.player.JoinOffer;
import xyz.nucleoid.plasmid.game.player.JoinOfferResult;
import xyz.nucleoid.stimuli.event.StimulusEvent;

import java.util.Collection;

/**
* Events relating to players being added and removed from a {@link GameSpace} or {@link GameActivity}.
*/
Expand Down Expand Up @@ -102,20 +97,19 @@ public final class GamePlayerEvents {
});

/**
* Called when a single {@link ServerPlayerEntity} tries to join this game. This event is responsible for bringing
* the player into the {@link GameSpace} world in the correct location.
* Called when a group of {@link ServerPlayerEntity} tries to join this game.
* <p>
* Games must respond to this event in order for a player to be able to join by returning either
* {@link JoinOffer#accept(ServerWorld)} or {@link JoinOffer#reject(Text)}.
* Games must respond to this event in order for players to be able to join by returning either
* {@link JoinOffer#accept()} or {@link JoinOffer#reject(Text)}.
*
* @see JoinOffer
* @see JoinOfferResult
* @see GamePlayerEvents#JOIN
* @see GamePlayerEvents#ACCEPT
*/
public static final StimulusEvent<Offer> OFFER = StimulusEvent.create(Offer.class, ctx -> offer -> {
try {
for (var listener : ctx.getListeners()) {
var result = listener.onOfferPlayer(offer);
var result = listener.onOfferPlayers(offer);
if (!(result instanceof JoinOfferResult.Pass)) {
return result;
}
Expand All @@ -127,6 +121,30 @@ public final class GamePlayerEvents {
}
});

/**
* Called when a group of {@link ServerPlayerEntity} is accepted to join this game. This event is responsible for bringing
* the players into the {@link GameSpace} world in the correct location.
* <p>
* Games must respond to this event in order for players to be able to join.
*
* @see JoinAcceptor
* @see JoinAcceptorResult
* @see GamePlayerEvents#JOIN
*/
public static final StimulusEvent<Accept> ACCEPT = StimulusEvent.create(Accept.class, ctx -> accept -> {
try {
for (var listener : ctx.getListeners()) {
var result = listener.onAcceptPlayers(accept);
if (!(result instanceof JoinAcceptorResult.Pass)) {
return result;
}
}
} catch (Throwable throwable) {
ctx.handleException(throwable);
}
return accept.pass();
});

/**
* Called when display name of {@link ServerPlayerEntity} is created.
* Can be used to manipulate it in game.
Expand All @@ -152,7 +170,11 @@ public interface Remove {
}

public interface Offer {
JoinOfferResult onOfferPlayer(JoinOffer offer);
JoinOfferResult onOfferPlayers(JoinOffer offer);
}

public interface Accept {
JoinAcceptorResult onAcceptPlayers(JoinAcceptor acceptor);
}

public interface Name {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package xyz.nucleoid.plasmid.game.manager;

import com.google.common.collect.Lists;
import com.mojang.authlib.GameProfile;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import me.lucko.fabric.api.permissions.v0.Permissions;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.registry.RegistryKey;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
Expand All @@ -19,11 +17,11 @@
import xyz.nucleoid.plasmid.game.config.GameConfig;
import xyz.nucleoid.plasmid.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
import xyz.nucleoid.plasmid.game.player.JoinIntent;
import xyz.nucleoid.plasmid.game.player.JoinAcceptorResult;
import xyz.nucleoid.plasmid.game.player.LocalJoinAcceptor;
import xyz.nucleoid.plasmid.game.player.LocalJoinOffer;
import xyz.nucleoid.plasmid.game.player.JoinOfferResult;

import java.util.Collection;
import java.util.Map;
import java.util.function.Consumer;

Expand Down Expand Up @@ -196,7 +194,7 @@ public GameBehavior getBehavior() {
return this.state;
}

JoinOfferResult offerPlayer(LocalJoinOffer offer) {
JoinOfferResult offerPlayers(LocalJoinOffer offer) {
if (this.closed) {
return offer.reject(GameTexts.Join.gameClosed());
} else if (offer.serverPlayers().stream().anyMatch(this.manager::inGame)) {
Expand All @@ -205,9 +203,14 @@ JoinOfferResult offerPlayer(LocalJoinOffer offer) {
return offer.reject(GameTexts.Join.notAllowed());
}

return this.state.invoker(GamePlayerEvents.OFFER).onOfferPlayer(offer);
return this.state.invoker(GamePlayerEvents.OFFER).onOfferPlayers(offer);
}

JoinAcceptorResult acceptPlayers(LocalJoinAcceptor acceptor) {
return this.state.invoker(GamePlayerEvents.ACCEPT).onAcceptPlayers(acceptor);
}


void onAddPlayer(ServerPlayerEntity player) {
this.state.propagatingInvoker(GamePlayerEvents.JOIN).onAddPlayer(player);
this.state.propagatingInvoker(GamePlayerEvents.ADD).onAddPlayer(player);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public GameResult simulateOffer(Collection<ServerPlayerEntity> players, JoinInte

var offer = new LocalJoinOffer(players, intent);

return switch (this.space.offerPlayer(offer)) {
return switch (this.space.offerPlayers(offer)) {
case JoinOfferResult.Accept accept -> GameResult.ok();
case JoinOfferResult.Reject reject -> GameResult.error(reject.reason());
default -> GameResult.error(GameTexts.Join.genericError());
Expand All @@ -57,29 +57,34 @@ private GameResult attemptOffer(Collection<ServerPlayerEntity> players, JoinInte

var offer = new LocalJoinOffer(players, intent);

switch (this.space.offerPlayer(offer)) {
case LocalJoinOffer.Accept accept -> {
return switch (this.space.offerPlayers(offer)) {
case JoinOfferResult.Accept accept -> this.accept(players, intent);
case JoinOfferResult.Reject reject -> GameResult.error(reject.reason());
default -> GameResult.error(GameTexts.Join.genericError());
};
}

private GameResult accept(Collection<ServerPlayerEntity> players, JoinIntent intent) {
var acceptor = new LocalJoinAcceptor(players, intent);

switch (this.space.acceptPlayers(acceptor)) {
case LocalJoinAcceptor.Teleport teleport -> {
try {
var joiningSet = new MutablePlayerSet(this.space.getServer());
for (var player : players) {
this.teleporter.teleportIn(player, accept::applyJoin);
this.teleporter.teleportIn(player, teleport::applyTeleport);
this.set.add(player);
this.space.onAddPlayer(player);
joiningSet.add(player);
}
accept.joinAll(joiningSet);
teleport.runCallbacks(joiningSet);

return GameResult.ok();
} catch (Throwable throwable) {
return GameResult.error(GameTexts.Join.unexpectedError());
}
}
case JoinOfferResult.Reject reject -> {
return GameResult.error(reject.reason());
}
default -> {
return GameResult.error(GameTexts.Join.genericError());
}
default -> throw new IllegalStateException("Accept event must be handled");
}
}

Expand Down
127 changes: 127 additions & 0 deletions src/main/java/xyz/nucleoid/plasmid/game/player/JoinAcceptor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package xyz.nucleoid.plasmid.game.player;

import com.mojang.authlib.GameProfile;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.Vec3d;
import xyz.nucleoid.plasmid.game.GameSpace;
import xyz.nucleoid.plasmid.game.event.GamePlayerEvents;
import xyz.nucleoid.plasmid.util.PlayerPos;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
* Represents an agent which is responsible for bringing a player or group of players
* into the {@link GameSpace} world in the correct location.
* <p>
* This object should be used in order to construct a {@link JoinAcceptorResult} object to return from a listener to the
* {@link GamePlayerEvents#ACCEPT} event.
*
* @see GameSpace
* @see GamePlayerEvents#ACCEPT
*/
public interface JoinAcceptor {
/**
* @return the set of {@link GameProfile} of the players that are joining to this {@link GameSpace}
*/
Set<GameProfile> players();

/**
* @return the {@link UUID profile UUID} of the players that are joining to this {@link GameSpace}
*/
default Set<UUID> playerIds() {
return this.players()
.stream()
.map(GameProfile::getId)
.collect(Collectors.toSet());
}

/**
* @return the usernames of the players that are joining to this {@link GameSpace}
*/
default Set<String> playerNames() {
return this.players()
.stream()
.map(GameProfile::getName)
.collect(Collectors.toSet());
}

/**
* @return the {@link JoinIntent 'intent'} of the players, such as whether they want to participate or spectate
* @see JoinIntent
*/
JoinIntent intent();

/**
* Returns a result that completes this join by teleporting the players.
* <p>
* The result of this function must be returned within a
* {@link GamePlayerEvents#ACCEPT} listener.
*
* @param positions the map of positions where the players should be teleported to
* @return a "teleport" result
* @throws IllegalArgumentException when positions are not specified for all joining players
* @see JoinAcceptorResult.Teleport#thenRun(Consumer)
* @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
*/
JoinAcceptorResult.Teleport teleport(Map<UUID, PlayerPos> positions);

/**
* Returns a result that completes this join by teleporting the players.
* <p>
* The result of this function must be returned within a
* {@link GamePlayerEvents#ACCEPT} listener.
*
* @param positions a function that for given player returns position where the player should be teleported to
* @return a "teleport" result
* @throws IllegalArgumentException when positions are not specified for all joining players
* @see JoinAcceptorResult.Teleport#thenRun(Consumer)
* @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
*/
JoinAcceptorResult.Teleport teleport(Function<GameProfile, PlayerPos> positions);

/**
* Returns a result that completes this join by teleporting the players.
* <p>
* The result of this function must be returned within a
* {@link GamePlayerEvents#ACCEPT} listener.
*
* @param world the world that all the players should be teleported to
* @param position the position that all the players should be teleported to
* @param yaw the 'yaw' angle that all the players should be teleported to
* @param pitch the 'pitch' angle that all the players should be teleported to
* @return a "teleport" result
* @see JoinAcceptorResult.Teleport#thenRun(Consumer)
* @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
*/
JoinAcceptorResult.Teleport teleport(ServerWorld world, Vec3d position, float yaw, float pitch);

/**
* Returns a result that completes this join by teleporting the players.
* <p>
* The result of this function must be returned within a
* {@link GamePlayerEvents#ACCEPT} listener.
*
* @param world the world that all the players should be teleported to
* @param position the position that all the players should be teleported to
* @return a "teleport" result
* @see JoinAcceptorResult.Teleport#thenRun(Consumer)
* @see JoinAcceptorResult.Teleport#thenRunForEach(Consumer)
*/
default JoinAcceptorResult.Teleport teleport(ServerWorld world, Vec3d position) {
return this.teleport(world, position, 0, 0);
}

/**
* Returns a result that does nothing, passing on any handling to any other listener.
*
* @return a "passing" result
*/
default JoinAcceptorResult pass() {
return JoinAcceptorResult.PASS;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package xyz.nucleoid.plasmid.game.player;

import net.minecraft.server.network.ServerPlayerEntity;

import java.util.function.Consumer;

public sealed interface JoinAcceptorResult permits JoinAcceptorResult.Pass, JoinAcceptorResult.Teleport {
Pass PASS = new Pass();

final class Pass implements JoinAcceptorResult {
private Pass() {
}
}

non-sealed interface Teleport extends JoinAcceptorResult {
Teleport thenRun(Consumer<PlayerSet> consumer);

default Teleport thenRunForEach(Consumer<ServerPlayerEntity> consumer) {
return this.thenRun(players -> players.forEach(consumer));
}
}
}
Loading

0 comments on commit a983800

Please sign in to comment.