Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul the waiting lobby user interface #303

Merged
merged 9 commits into from
Nov 15, 2024
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.nucleoid.plasmid.api.game.common;

import eu.pb4.polymer.core.api.utils.PolymerUtils;
import net.minecraft.entity.boss.BossBar;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.server.network.ServerPlayerEntity;
Expand All @@ -13,14 +14,18 @@
import xyz.nucleoid.plasmid.api.game.*;
import xyz.nucleoid.plasmid.api.game.common.config.WaitingLobbyConfig;
import xyz.nucleoid.plasmid.api.game.common.team.TeamSelectionLobby;
import xyz.nucleoid.plasmid.api.game.common.ui.WaitingLobbyUiLayout;
import xyz.nucleoid.plasmid.api.game.common.widget.BossBarWidget;
import xyz.nucleoid.plasmid.api.game.config.GameConfig;
import xyz.nucleoid.plasmid.api.game.event.GameActivityEvents;
import xyz.nucleoid.plasmid.api.game.event.GamePlayerEvents;
import xyz.nucleoid.plasmid.api.game.event.GameWaitingLobbyEvents;
import xyz.nucleoid.plasmid.api.game.player.JoinOffer;
import xyz.nucleoid.plasmid.api.game.player.JoinOfferResult;
import xyz.nucleoid.plasmid.api.game.rule.GameRuleType;
import xyz.nucleoid.plasmid.api.game.common.widget.SidebarWidget;
import xyz.nucleoid.plasmid.impl.game.common.ui.WaitingLobbyUi;
import xyz.nucleoid.plasmid.impl.game.common.ui.element.LeaveGameWaitingLobbyUiElement;
import xyz.nucleoid.plasmid.impl.game.manager.GameSpaceManagerImpl;
import xyz.nucleoid.plasmid.impl.compatibility.AfkDisplayCompatibility;

Expand Down Expand Up @@ -89,7 +94,9 @@ public static GameWaitingLobby addTo(GameActivity activity, WaitingLobbyConfig p
activity.listen(GameActivityEvents.TICK, lobby::onTick);
activity.listen(GameActivityEvents.REQUEST_START, lobby::requestStart);
activity.listen(GamePlayerEvents.OFFER, lobby::offerPlayer);
activity.listen(GamePlayerEvents.ADD, lobby::onAddPlayer);
activity.listen(GamePlayerEvents.REMOVE, lobby::onRemovePlayer);
activity.listen(GameWaitingLobbyEvents.BUILD_UI_LAYOUT, lobby::onBuildUiLayout);

activity.listen(GameActivityEvents.STATE_UPDATE, lobby::updateState);

Expand Down Expand Up @@ -155,6 +162,11 @@ private void onTick() {
this.started = false;
this.startRequested = false;
this.countdownStart = -1;
} else {
for (var player : this.gameSpace.getPlayers()) {
player.closeHandledScreen();
PolymerUtils.reloadInventory(player);
}
}
}
}
Expand All @@ -180,10 +192,19 @@ private JoinOfferResult offerPlayer(JoinOffer offer) {
return offer.pass();
}

private void onAddPlayer(ServerPlayerEntity player) {
var ui = new WaitingLobbyUi(player, this.gameSpace);
ui.open();
}

private void onRemovePlayer(ServerPlayerEntity player) {
this.updateCountdown();
}

private void onBuildUiLayout(WaitingLobbyUiLayout layout, ServerPlayerEntity player) {
layout.addTrailing(new LeaveGameWaitingLobbyUiElement(this.gameSpace, player));
}

private void updateCountdown() {
long targetDuration = this.getTargetCountdownDuration();
if (targetDuration != this.countdownDuration) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
package xyz.nucleoid.plasmid.api.game.common.team;

import com.mojang.serialization.Codec;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMap;
import it.unimi.dsi.fastutil.objects.Reference2IntMaps;
import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.NbtComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NbtOps;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Formatting;
import net.minecraft.util.Hand;
import xyz.nucleoid.plasmid.api.game.common.GameWaitingLobby;
import xyz.nucleoid.plasmid.impl.Plasmid;
import xyz.nucleoid.plasmid.api.game.GameActivity;
import xyz.nucleoid.plasmid.api.game.event.GamePlayerEvents;
import xyz.nucleoid.plasmid.api.game.common.GameWaitingLobby;
import xyz.nucleoid.plasmid.api.game.common.ui.WaitingLobbyUiLayout;
import xyz.nucleoid.plasmid.api.game.event.GameWaitingLobbyEvents;
import xyz.nucleoid.plasmid.api.game.player.PlayerIterable;
import xyz.nucleoid.plasmid.api.util.ColoredBlocks;
import xyz.nucleoid.stimuli.event.item.ItemUseEvent;
import xyz.nucleoid.plasmid.impl.game.common.ui.element.TeamSelectionWaitingLobbyUiElement;

import java.util.Map;
import java.util.UUID;
Expand All @@ -40,8 +30,6 @@
* @see GameWaitingLobby
*/
public final class TeamSelectionLobby {
private static final String TEAM_KEY = Plasmid.ID + ":team";

private final GameTeamList teams;

private final Reference2IntMap<GameTeamKey> maxTeamSize = new Reference2IntOpenHashMap<>();
Expand All @@ -61,8 +49,7 @@ private TeamSelectionLobby(GameTeamList teams) {
*/
public static TeamSelectionLobby addTo(GameActivity activity, GameTeamList teams) {
var lobby = new TeamSelectionLobby(teams);
activity.listen(GamePlayerEvents.ADD, lobby::onAddPlayer);
activity.listen(ItemUseEvent.EVENT, lobby::onUseItem);
activity.listen(GameWaitingLobbyEvents.BUILD_UI_LAYOUT, lobby::onBuildUiLayout);

return lobby;
}
Expand All @@ -77,46 +64,23 @@ public void setSizeForTeam(GameTeamKey team, int size) {
this.maxTeamSize.put(team, size);
}

private void onAddPlayer(ServerPlayerEntity player) {
int index = 0;

for (var team : this.teams) {
var config = team.config();
var name = Text.translatable("text.plasmid.team_selection.request_team", config.name())
.formatted(Formatting.BOLD, config.chatFormatting());

var stack = new ItemStack(ColoredBlocks.wool(config.blockDyeColor()));
stack.set(DataComponentTypes.ITEM_NAME, name);

stack.set(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT.with(NbtOps.INSTANCE, Codec.STRING.fieldOf(TEAM_KEY), team.key().id()).getOrThrow());

player.getInventory().setStack(index++, stack);
}
}

private ActionResult onUseItem(ServerPlayerEntity player, Hand hand) {
var stack = player.getStackInHand(hand);

if (stack.isIn(ItemTags.WOOL)) {
var key = new GameTeamKey(stack.getOrDefault(DataComponentTypes.CUSTOM_DATA, NbtComponent.DEFAULT)
.get(Codec.STRING.fieldOf(TEAM_KEY)).getOrThrow());

private void onBuildUiLayout(WaitingLobbyUiLayout layout, ServerPlayerEntity player) {
layout.addLeading(new TeamSelectionWaitingLobbyUiElement(teams, key -> {
haykam821 marked this conversation as resolved.
Show resolved Hide resolved
return key == this.teamPreference.get(player.getUuid());
}, key -> {
var team = this.teams.byKey(key);
if (team != null) {
var config = team.config();

this.teamPreference.put(player.getUuid(), key);
layout.refresh();

var message = Text.translatable("text.plasmid.team_selection.requested_team",
Text.translatable("text.plasmid.team_selection.suffixed_team", config.name()).formatted(config.chatFormatting()));

player.sendMessage(message, false);

return ActionResult.SUCCESS_SERVER;
}
}

return ActionResult.PASS;
}));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package xyz.nucleoid.plasmid.api.game.common.ui;

import java.util.List;
import java.util.SequencedCollection;

import eu.pb4.sgui.api.ClickType;
import eu.pb4.sgui.api.elements.GuiElementInterface;
import eu.pb4.sgui.api.gui.HotbarGui;
import eu.pb4.sgui.api.gui.SlotGuiInterface;

public interface WaitingLobbyUiElement {
GuiElementInterface createMainElement();

default SequencedCollection<GuiElementInterface> createExtendedElements() {
return List.of(this.createMainElement());
}

static boolean isClick(ClickType type, SlotGuiInterface gui) {
return type.isRight || !(gui instanceof HotbarGui);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package xyz.nucleoid.plasmid.api.game.common.ui;

import eu.pb4.sgui.api.elements.GuiElementInterface;
import xyz.nucleoid.plasmid.impl.game.common.ui.WaitingLobbyUiLayoutImpl;

import java.util.Objects;
import java.util.function.Consumer;

public interface WaitingLobbyUiLayout {
void addLeading(WaitingLobbyUiElement element);

void addTrailing(WaitingLobbyUiElement element);

void refresh();

static WaitingLobbyUiLayout of(Consumer<GuiElementInterface[]> callback) {
return new WaitingLobbyUiLayoutImpl(Objects.requireNonNull(callback));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package xyz.nucleoid.plasmid.api.game.event;

import net.minecraft.server.network.ServerPlayerEntity;
import xyz.nucleoid.plasmid.api.game.GameActivity;
import xyz.nucleoid.plasmid.api.game.GameSpace;
import xyz.nucleoid.plasmid.api.game.common.GameWaitingLobby;
import xyz.nucleoid.plasmid.api.game.common.ui.WaitingLobbyUiLayout;
import xyz.nucleoid.stimuli.event.StimulusEvent;

/**
* Events relating to a {@link GameWaitingLobby} applied to a {@link GameActivity} within a {@link GameSpace}.
*/
public final class GameWaitingLobbyEvents {
/**
* Called when a {@link WaitingLobbyUiLayout} is created for a specific player's waiting lobby UI.
* <p>
* This event should be used to add custom UI elements to the hotbar UI
* used by players before the game begins.
*/
public static final StimulusEvent<BuildUiLayout> BUILD_UI_LAYOUT = StimulusEvent.create(BuildUiLayout.class, ctx -> (layout, player) -> {
try {
for (var listener : ctx.getListeners()) {
listener.onBuildUiLayout(layout, player);
}
} catch (Throwable throwable) {
ctx.handleException(throwable);
}
});

public interface BuildUiLayout {
void onBuildUiLayout(WaitingLobbyUiLayout layout, ServerPlayerEntity player);
}
}
33 changes: 26 additions & 7 deletions src/main/java/xyz/nucleoid/plasmid/api/util/Guis.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package xyz.nucleoid.plasmid.api.util;

import eu.pb4.sgui.api.ClickType;
import eu.pb4.sgui.api.SlotHolder;
import eu.pb4.sgui.api.elements.GuiElementInterface;
import eu.pb4.sgui.api.gui.SimpleGui;
Expand All @@ -13,6 +14,7 @@
import net.minecraft.registry.*;
import net.minecraft.screen.ScreenHandlerType;
import net.minecraft.screen.ScreenTexts;
import net.minecraft.screen.slot.SlotActionType;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.MutableText;
import net.minecraft.util.DyeColor;
Expand All @@ -21,29 +23,46 @@
import org.jetbrains.annotations.Range;

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

public final class Guis {
private Guis() {
}

public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, boolean includePlayerSlots, GuiElementInterface... elements) {
var gui = new SimpleGui(selectScreenType(elements.length), player, includePlayerSlots);
public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, boolean includePlayerSlots, Consumer<SimpleGui> onClick, Consumer<SimpleGui> onClose, GuiElementInterface... elements) {
var gui = new SimpleGui(selectScreenType(elements.length), player, includePlayerSlots) {
@Override
public boolean onClick(int index, ClickType type, SlotActionType action, GuiElementInterface element) {
onClick.accept(this);
return super.onClick(index, type, action, element);
}

@Override
public void onClose() {
onClose.accept(this);
}
};

gui.setTitle(text);

buildSelector(gui, elements);
return gui;
}

public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, GuiElementInterface... elements) {
return createSelectorGui(player, text, false, elements);
public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, Consumer<SimpleGui> onClick, Consumer<SimpleGui> onClose, GuiElementInterface... elements) {
return createSelectorGui(player, text, false, onClick, onClose, elements);
}

public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, Consumer<SimpleGui> onClick, Consumer<SimpleGui> onClose, Collection<GuiElementInterface> elements) {
return createSelectorGui(player, text, false, onClick, onClose, elements);
}

public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, Collection<GuiElementInterface> elements) {
return createSelectorGui(player, text, false, elements);
public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, boolean includePlayerSlots, Consumer<SimpleGui> onClick, Consumer<SimpleGui> onClose, Collection<GuiElementInterface> elements) {
return createSelectorGui(player, text, includePlayerSlots, onClick, onClose, elements.toArray(new GuiElementInterface[0]));
}

public static SimpleGui createSelectorGui(ServerPlayerEntity player, MutableText text, boolean includePlayerSlots, Collection<GuiElementInterface> elements) {
return createSelectorGui(player, text, includePlayerSlots, elements.toArray(new GuiElementInterface[0]));
return createSelectorGui(player, text, includePlayerSlots, gui -> {}, gui -> {}, elements.toArray(new GuiElementInterface[0]));
}

public static Layer createSelectorLayer(int height, int width, Collection<GuiElementInterface> elements) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package xyz.nucleoid.plasmid.impl.game.common.ui;

import eu.pb4.sgui.api.elements.GuiElementInterface;
import eu.pb4.sgui.api.gui.SlotGuiInterface;
import net.minecraft.item.ItemStack;
import xyz.nucleoid.plasmid.api.game.common.ui.WaitingLobbyUiElement;
import xyz.nucleoid.plasmid.api.util.Guis;

public record ExtensionGuiElement(GuiElementInterface delegate, WaitingLobbyUiLayoutEntry entry) implements GuiElementInterface {
@Override
public ItemStack getItemStack() {
return this.delegate.getItemStack();
}

@Override
public ClickCallback getGuiCallback() {
return (index, type, action, gui) -> {
if (WaitingLobbyUiElement.isClick(type, gui)) {
this.openExtendedGui(gui);
}
};
}

private void openExtendedGui(SlotGuiInterface parent) {
var player = parent.getPlayer();
var name = this.delegate.getItemStackForDisplay(parent).getName().copy();

var ui = Guis.createSelectorGui(player, name, true, gui -> {
if (gui.isOpen()) {
// Refresh elements
this.openExtendedGui(parent);
}
}, gui -> {
parent.open();
}, this.entry.getElement().createExtendedElements());

ui.open();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package xyz.nucleoid.plasmid.impl.game.common.ui;

import eu.pb4.sgui.api.gui.HotbarGui;
import net.minecraft.server.network.ServerPlayerEntity;
import xyz.nucleoid.plasmid.api.game.GameSpace;
import xyz.nucleoid.plasmid.api.game.common.ui.WaitingLobbyUiLayout;
import xyz.nucleoid.plasmid.api.game.event.GameWaitingLobbyEvents;
import xyz.nucleoid.stimuli.Stimuli;

public class WaitingLobbyUi extends HotbarGui {
public WaitingLobbyUi(ServerPlayerEntity player, GameSpace gameSpace) {
super(player);

var layout = WaitingLobbyUiLayout.of(elements -> {
int index = 0;

for (var element : elements) {
this.setSlot(index, element);
index += 1;
}
});

try (var invokers = Stimuli.select().forEntity(player)) {
invokers.get(GameWaitingLobbyEvents.BUILD_UI_LAYOUT).onBuildUiLayout(layout, player);
}

layout.refresh();
}

@Override
public boolean canPlayerClose() {
return false;
}
}
Loading
Loading