Skip to content

Commit

Permalink
Try to make build mode a bit more robust against desyncing state
Browse files Browse the repository at this point in the history
  • Loading branch information
Gegy committed Nov 3, 2023
1 parent 7903605 commit f430088
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ public void onBreakSpeed(final PlayerEvent.BreakSpeed event) {
public static boolean isActive(Player player) {
if (player.level().isClientSide()) {
return GBClientState.isActive();
} else {
return GBServerState.isActiveFor(player);
}
return player instanceof ServerPlayer sp ? GBServerState.isActiveFor(sp) : GBPlayerStore.isActive(player);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.lovetropics.gamemodebuild.state.GBClientState;
import com.lovetropics.gamemodebuild.state.GBServerState;
import com.lovetropics.gamemodebuild.state.GBServerState.NotificationType;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.api.distmarker.Dist;
Expand All @@ -13,28 +12,24 @@

import java.util.function.Supplier;

public record SetActiveMessage(boolean enabled) {
public record SetActiveMessage(boolean active) {
public SetActiveMessage(FriendlyByteBuf input) {
this(input.readBoolean());
}

public void serialize(FriendlyByteBuf output) {
output.writeBoolean(enabled);
output.writeBoolean(active);
}

public void handle(Supplier<NetworkEvent.Context> ctxSupplier) {
NetworkEvent.Context ctx = ctxSupplier.get();
if (ctx.getDirection() == NetworkDirection.PLAY_TO_SERVER) {
ServerPlayer player = ctx.getSender();
if (player != null) {
if (GBServerState.isEnabledFor(player)) {
GBServerState.setActiveFor(player, enabled);
} else {
GBServerState.notifyPlayerActivity(false, player, NotificationType.ACTIVE);
}
GBServerState.requestActive(player, active);
}
} else if (ctx.getDirection() == NetworkDirection.PLAY_TO_CLIENT) {
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> setClientState(enabled));
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> setClientState(active));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;

import java.util.ArrayList;
import java.util.List;

public final class GBPlayerStore {
private static final String KEY_ACTIVE = "active";
private static final String KEY_ENABLED = "enabled";
private static final String KEY_PLAYER_INVENTORY = "playerinv";
private static final String KEY_SP_INVENTORY = "buildinv";
private static final String KEY_BUILD_INVENTORY = "buildinv";
private static final String KEY_LIST = "list";

public static void setEnabled(Player player, boolean enabled) {
Expand Down Expand Up @@ -52,27 +51,36 @@ public static String getList(Player player) {
return survivalPlus.contains(KEY_LIST) ? survivalPlus.getString(KEY_LIST) : "default";
}

private static void switchInventories(Player player, String from, String to) {
final Inventory inventory = player.getInventory();
CompoundTag survivalPlus = getOrCreatePersistent(player, GamemodeBuild.MODID);
ListTag list = new ListTag();
inventory.save(list);
survivalPlus.put(from, list);
List<ItemStack> armor = new ArrayList<>(inventory.armor);
inventory.clearContent();

inventory.load(survivalPlus.getList(to, Tag.TAG_COMPOUND));
for (int i = 0; i < armor.size(); i++) {
inventory.armor.set(i, armor.get(i));
public static void switchToInventory(Player player, boolean buildMode) {
if (buildMode) {
switchInventories(player, KEY_PLAYER_INVENTORY, KEY_BUILD_INVENTORY);
} else {
switchInventories(player, KEY_BUILD_INVENTORY, KEY_PLAYER_INVENTORY);
}
}

public static void switchToSPInventory(Player player) {
switchInventories(player, KEY_PLAYER_INVENTORY, KEY_SP_INVENTORY);
private static void switchInventories(Player player, String from, String to) {
ListTag currentInventory = new ListTag();
player.getInventory().save(currentInventory);
ListTag newInventory = swapInventoryTag(player, from, to, currentInventory);
loadInventory(player.getInventory(), newInventory);
}

public static void switchToPlayerInventory(Player player) {
switchInventories(player, KEY_SP_INVENTORY, KEY_PLAYER_INVENTORY);
private static ListTag swapInventoryTag(Player player, String from, String to, ListTag inventory) {
CompoundTag tag = getOrCreatePersistent(player, GamemodeBuild.MODID);
ListTag newInventory = tag.getList(to, Tag.TAG_COMPOUND);
tag.remove(to);
tag.put(from, inventory);
return newInventory;
}

private static void loadInventory(Inventory inventory, ListTag tag) {
List<ItemStack> armor = List.copyOf(inventory.armor);
inventory.clearContent();
inventory.load(tag);
for (int i = 0; i < armor.size(); i++) {
inventory.armor.set(i, armor.get(i));
}
}

private static CompoundTag getOrCreatePersistent(Player player, String key) {
Expand Down
106 changes: 47 additions & 59 deletions src/main/java/com/lovetropics/gamemodebuild/state/GBServerState.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.lovetropics.gamemodebuild.GamemodeBuild;
import com.lovetropics.gamemodebuild.message.GBNetwork;
import com.lovetropics.gamemodebuild.message.SetActiveMessage;
import it.unimi.dsi.fastutil.objects.Reference2BooleanMap;
import it.unimi.dsi.fastutil.objects.Reference2BooleanOpenHashMap;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
Expand All @@ -15,89 +17,75 @@

@Mod.EventBusSubscriber(modid = GamemodeBuild.MODID)
public final class GBServerState {

public enum NotificationType {

INITIAL,
ACTIVE,
ENABLED,
;
}

public static void setGloballyEnabled(MinecraftServer server, boolean enabled) {
GBConfigs.SERVER.enable(enabled);

if (enabled == isGloballyEnabled()) {
return;
}

Reference2BooleanMap<ServerPlayer> activeMap = new Reference2BooleanOpenHashMap<>();
for (ServerPlayer player : server.getPlayerList().getPlayers()) {
notifyPlayerActivity(false, player, NotificationType.ENABLED);
activeMap.put(player, isActiveFor(player));
}

GBConfigs.SERVER.enable(enabled);
activeMap.forEach((player, wasActive) -> notifyPlayerActivity(wasActive, player));
}

public static void setEnabledFor(ServerPlayer player, boolean enabled) {
boolean wasActive = isActiveFor(player);
GBPlayerStore.setEnabled(player, enabled);
notifyPlayerActivity(wasActive, player, NotificationType.ENABLED);
notifyPlayerActivity(wasActive, player);
}

public static boolean isGloballyEnabled() {
return GBConfigs.SERVER.enabled();
}

public static boolean isEnabledFor(ServerPlayer player) {
if (!isGloballyEnabled()) {
return false;
}
return GBPlayerStore.isEnabled(player);
public static boolean isEnabledFor(Player player) {
return isGloballyEnabled() && GBPlayerStore.isEnabled(player);
}

public static void setActiveFor(ServerPlayer player, boolean active) {
if (isEnabledFor(player) || !active) {
boolean wasActive = isActiveFor(player);
GBPlayerStore.setActive(player, active);
notifyPlayerActivity(wasActive, player, NotificationType.ACTIVE);
public static void requestActive(ServerPlayer player, boolean active) {
if (!isEnabledFor(player)) {
notifyDisabled(player);
return;
}
boolean wasActive = isActiveFor(player);
GBPlayerStore.setActive(player, active);
notifyPlayerActivity(wasActive, player);
}
public static boolean isActiveFor(ServerPlayer player) {

public static boolean isActiveFor(Player player) {
return isEnabledFor(player) && GBPlayerStore.isActive(player);
}

public static void switchInventories(ServerPlayer player, boolean state) {
if (state) {
GBPlayerStore.switchToSPInventory(player);
} else {
GBPlayerStore.switchToPlayerInventory(player);
}
}

public static void notifyPlayerActivity(boolean prevState, ServerPlayer player, NotificationType type) {

private static void notifyPlayerActivity(boolean prevState, ServerPlayer player) {
boolean state = isActiveFor(player);
if (type != NotificationType.INITIAL) {
if (!GBServerState.isEnabledFor(player) && type == NotificationType.ACTIVE) {
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " is disabled!"), true);
} else if (prevState != state) {
GBServerState.switchInventories(player, state);
if (state) {
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " activated"), true);
} else {
// // Clear marked stacks from inventory
// for (int i = 0; i < player.inventory.getSizeInventory(); i++) {
// if (SPStackMarker.isMarked(player.inventory.getStackInSlot(i))) {
// player.inventory.removeStackFromSlot(i);
// }
// }
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " deactivated"), true);
}
}
}
SetActiveMessage message = new SetActiveMessage(state);
if (prevState == state) {
return;
}
GBPlayerStore.switchToInventory(player, state);
if (state) {
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " activated"), true);
} else {
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " deactivated"), true);
}
sendPlayerState(player);
}

public static void notifyDisabled(ServerPlayer player) {
player.displayClientMessage(Component.literal(GamemodeBuild.NAME + " is disabled!"), true);
}

public static void sendPlayerState(ServerPlayer player) {
SetActiveMessage message = new SetActiveMessage(isActiveFor(player));
GBNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), message);
}

@SubscribeEvent
public static void onPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) {
if (event.getEntity() instanceof ServerPlayer player) {
// Previous state doesn't matter here
notifyPlayerActivity(false, player, NotificationType.INITIAL);
sendPlayerState(player);
}
}
}

0 comments on commit f430088

Please sign in to comment.