feat: role configuration screen and new role class properties

This commit is contained in:
Ninjdai 2024-10-02 10:58:30 +02:00
parent aecce6e9b7
commit ba72073bb1
17 changed files with 416 additions and 24 deletions

View File

@ -1,7 +1,9 @@
package dev.ninjdai.werewolf;
import dev.ninjdai.werewolf.commands.LGCommand;
import dev.ninjdai.werewolf.commands.GamemodeCommand;
import dev.ninjdai.werewolf.commands.lg.LGCommand;
import dev.ninjdai.werewolf.commands.PlayerKillCommand;
import dev.ninjdai.werewolf.gui.Gui;
import dev.ninjdai.werewolf.uhc.UHCPlayer;
import dev.ninjdai.werewolf.worldgen.LGGenerator;
import io.github.togar2.pvp.MinestomPvP;
@ -10,6 +12,7 @@ import io.github.togar2.pvp.feature.CombatFeatures;
import net.minestom.server.MinecraftServer;
import net.minestom.server.event.GlobalEventHandler;
import net.minestom.server.extras.lan.OpenToLAN;
import net.minestom.server.extras.velocity.VelocityProxy;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.LightingChunk;
@ -35,9 +38,11 @@ public class Main {
GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler();
EventHandler.register(globalEventHandler);
globalEventHandler.addChild(Gui.node);
MinecraftServer.getCommandManager().register(new LGCommand());
MinecraftServer.getCommandManager().register(new PlayerKillCommand());
MinecraftServer.getCommandManager().register(new GamemodeCommand());
MinestomPvP.init();
CombatFeatureSet legacyVanilla = CombatFeatures.legacyVanilla();

View File

@ -0,0 +1,27 @@
package dev.ninjdai.werewolf.commands;
import dev.ninjdai.werewolf.uhc.UHCPlayer;
import dev.ninjdai.werewolf.uhc.roles.Roles;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentEnum;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.ArgumentEnum.Format;
import net.minestom.server.entity.GameMode;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class GamemodeCommand extends Command {
static final ArgumentEnum<GameMode> gamemodeArgument = ArgumentType.Enum("gamemode", GameMode.class).setFormat(Format.LOWER_CASED);
public GamemodeCommand() {
super("gamemode", "gm");
setDefaultExecutor((sender, context) -> {
sender.sendMessage("LG UHC");
});
addSyntax((sender, context) -> {
if(! (sender instanceof UHCPlayer player)) return;
player.setGameMode(context.get(gamemodeArgument));
}, gamemodeArgument);
}
}

View File

@ -1,4 +1,4 @@
package dev.ninjdai.werewolf.commands;
package dev.ninjdai.werewolf.commands.lg;
import net.minestom.server.command.builder.Command;
public class LGCommand extends Command {
@ -11,5 +11,7 @@ public class LGCommand extends Command {
});
addSubcommand(new LGTeamCommand());
addSubcommand(new LGRoleCommand());
addSubcommand(new LGConfiguration());
}
}

View File

@ -0,0 +1,81 @@
package dev.ninjdai.werewolf.commands.lg;
import dev.ninjdai.werewolf.gui.Gui;
import dev.ninjdai.werewolf.gui.GuiItem;
import dev.ninjdai.werewolf.gui.GuiItems;
import dev.ninjdai.werewolf.gui.StackedGui;
import dev.ninjdai.werewolf.uhc.UHCConfig;
import dev.ninjdai.werewolf.uhc.roles.Role;
import dev.ninjdai.werewolf.uhc.roles.Roles;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.MinecraftServer;
import net.minestom.server.command.builder.Command;
import net.minestom.server.component.DataComponentMap;
import net.minestom.server.entity.Player;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
public class LGConfiguration extends Command {
public static final UHCConfig config = new UHCConfig();
private static final Gui lgConfig = new StackedGui(InventoryType.CHEST_6_ROW, Component.text("LG Configuration", NamedTextColor.DARK_RED));
private static final Gui lgRoleConfig = new StackedGui(
InventoryType.CHEST_6_ROW,
Component.text("LG Configuration - Roles", NamedTextColor.DARK_RED),
lgConfig
);
static {
lgConfig.addGuiItem(4, 2, new GuiItem(
ItemStack.of(Material.PAINTING).withCustomName(Component.text("Roles", NamedTextColor.YELLOW).decoration(TextDecoration.ITALIC, false)),
event -> {
event.setCancelled(true);
event.getPlayer().openInventory(lgRoleConfig);
}
));
int roleSlot = 0;
for (Roles role: Roles.values()) {
final int finalRoleSlot = roleSlot;
lgRoleConfig.addGuiItem(roleSlot, new GuiItem(
ItemStack.of(Material.RED_TERRACOTTA).withCustomName(role.get().getName()).withLore(role.get().getDescription()),
(event, guiItem) -> config.activatedRoles.compute(role.get(), (_r, integer) -> {
if (integer==null) integer = 0;
switch (event.getClickType()) {
case LEFT_CLICK -> integer = Math.min(integer+1, role.get().getRoleMaxCount());
case RIGHT_CLICK -> integer = Math.max(integer-1, 0);
}
if (integer > 0) guiItem.setItemStack(guiItem.getItemStack().withMaterial(Material.GREEN_TERRACOTTA).withAmount(integer));
else guiItem.setItemStack(guiItem.getItemStack().withMaterial(Material.RED_TERRACOTTA).withAmount(1));
lgRoleConfig.updateItemStack(finalRoleSlot);
event.getPlayer().sendMessage(Component.text(String.format("There will be %d ", integer)).append(role.get().getName()).append(Component.text(" in the game")));
return integer;
})
));
roleSlot++;
}
}
public LGConfiguration() {
super("configure");
setDefaultExecutor((sender, context) -> {
if (!(sender instanceof Player player)){
sender.sendMessage("You're not a player !");
return;
}
player.openInventory(lgConfig);
});
}
}

View File

@ -1,4 +1,4 @@
package dev.ninjdai.werewolf.commands;
package dev.ninjdai.werewolf.commands.lg;
import dev.ninjdai.werewolf.uhc.UHCPlayer;
import dev.ninjdai.werewolf.uhc.roles.Role;
@ -10,11 +10,12 @@ import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentEnum;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.ArgumentEnum.Format;
import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity;
import net.minestom.server.utils.entity.EntityFinder;
public class LGRoleCommand extends Command {
static final ArgumentEnum<Roles> roleArgument = ArgumentType.Enum("target_role", Roles.class);
static final ArgumentEnum<Roles> roleArgument = ArgumentType.Enum("target_role", Roles.class).setFormat(Format.LOWER_CASED);
static final ArgumentEntity singlePlayerArgument = ArgumentType.Entity("player").onlyPlayers(true).singleEntity(true);
public LGRoleCommand() {
@ -43,7 +44,7 @@ public class LGRoleCommand extends Command {
sender.sendMessage(Component.empty().style(Style.style(TextDecoration.ITALIC))
.append(Component.text(player.getUsername()))
.append(Component.text(" now has role ", TextColor.color(94,94,94)))
.append(Component.text(role.getName()))
.append(role.getName())
);
}), roleArgument, singlePlayerArgument);
}
@ -64,7 +65,7 @@ public class LGRoleCommand extends Command {
sender.sendMessage(Component.empty().style(Style.style(TextDecoration.ITALIC))
.append(Component.text(player.getUsername()))
.append(Component.text(" has role ", TextColor.color(94,94,94)))
.append(Component.text(role.getName()))
.append(role.getName())
);
}), singlePlayerArgument);
}

View File

@ -1,4 +1,4 @@
package dev.ninjdai.werewolf.commands;
package dev.ninjdai.werewolf.commands.lg;
import dev.ninjdai.werewolf.uhc.UHCPlayer;
import dev.ninjdai.werewolf.uhc.teams.Team;
@ -10,11 +10,12 @@ import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.command.builder.Command;
import net.minestom.server.command.builder.arguments.ArgumentEnum;
import net.minestom.server.command.builder.arguments.ArgumentType;
import net.minestom.server.command.builder.arguments.ArgumentEnum.Format;
import net.minestom.server.command.builder.arguments.minecraft.ArgumentEntity;
import net.minestom.server.utils.entity.EntityFinder;
public class LGTeamCommand extends Command {
static final ArgumentEnum<Teams> teamArgument = ArgumentType.Enum("target_team", Teams.class);
static final ArgumentEnum<Teams> teamArgument = ArgumentType.Enum("target_team", Teams.class).setFormat(Format.LOWER_CASED);
static final ArgumentEntity singlePlayerArgument = ArgumentType.Entity("player").onlyPlayers(true).singleEntity(true);
public LGTeamCommand() {

View File

@ -0,0 +1,65 @@
package dev.ninjdai.werewolf.gui;
import net.kyori.adventure.text.Component;
import net.minestom.server.event.EventFilter;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.event.trait.InventoryEvent;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class Gui extends Inventory {
public static EventNode<InventoryEvent> node = EventNode.type("gui-node", EventFilter.INVENTORY);
public HashMap<Integer, GuiItem> slotItems = new HashMap<>();
public Gui(@NotNull InventoryType inventoryType, @NotNull Component title) {
super(inventoryType, title);
node.addListener(InventoryPreClickEvent.class, e -> {
if (e.getInventory() == this) {
e.setCancelled(true);
if (slotItems.containsKey(e.getSlot())) {
slotItems.get(e.getSlot()).getEvent().accept(e, slotItems.get(e.getSlot()));
}
}
});
}
public void updateItemStack(int slot) {
if(slotItems.containsKey(slot)) {
this.setItemStack(slot, slotItems.get(slot).getItemStack());
}
}
public void addGuiItem(int slot, @NotNull GuiItem item) {
this.setItemStack(slot, item.getItemStack());
if (item.getEvent() != null) {
slotItems.put(slot, item);
}
}
public void addGuiItem(int x, int y, @NotNull GuiItem item) {
addGuiItem(x + (y * 9), item);
}
public boolean isSlotFree(int slot) {
return this.getItemStack(slot).isAir();
}
public void applyMask(Mask mask, ItemStack maskItem) {
for (int i = 0; i < mask.getWidth(); i++) {
for (int j = 0; j < mask.getHeight(); j++) {
if (mask.isMasked(i, j)) {
if (this.isSlotFree(i + (j * 9))) this.addGuiItem(i + (j * 9), new GuiItem(maskItem));
}
}
}
}
}

View File

@ -0,0 +1,31 @@
package dev.ninjdai.werewolf.gui;
import lombok.Getter;
import lombok.Setter;
import net.minestom.server.event.inventory.InventoryPreClickEvent;
import net.minestom.server.item.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Getter
public class GuiItem {
@Setter private ItemStack itemStack;
private final BiConsumer<InventoryPreClickEvent, GuiItem> event;
public GuiItem(ItemStack itemStack) {
this.itemStack = itemStack;
this.event = (inventoryPreClickEvent, g) -> inventoryPreClickEvent.setCancelled(true);
}
public GuiItem(@NotNull ItemStack itemStack, Consumer<InventoryPreClickEvent> event) {
this.itemStack = itemStack;
this.event = ((inventoryPreClickEvent, item) -> event.accept(inventoryPreClickEvent));
}
public GuiItem(@NotNull ItemStack itemStack, BiConsumer<InventoryPreClickEvent, GuiItem> event) {
this.itemStack = itemStack;
this.event = event;
}
}

View File

@ -0,0 +1,18 @@
package dev.ninjdai.werewolf.gui;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
public class GuiItems {
public static GuiItem CLOSE_INVENTORY = new GuiItem(
ItemStack.of(Material.BARRIER).withCustomName(Component.text("Close", NamedTextColor.RED).decoration(TextDecoration.ITALIC, false)),
event -> {
event.setCancelled(true);
event.getPlayer().closeInventory();
}
);
public static GuiItem GLASS_PANE_FILLER = new GuiItem(ItemStack.of(Material.BLACK_STAINED_GLASS_PANE).withCustomName(Component.text("")));
}

View File

@ -0,0 +1,48 @@
package dev.ninjdai.werewolf.gui;
import org.jetbrains.annotations.NotNull;
public class Mask {
/**
* A two-dimensional array of booleans indicating which slots are 'enabled' and which ones are 'disabled'. This
* two-dimensional array is constructed in a row-major order fashion.
*/
private final boolean[][] mask;
public Mask(@NotNull String... mask) {
this.mask = new boolean[mask.length][mask.length == 0 ? 0 : mask[0].length()];
for (int row = 0; row < mask.length; row++) {
int length = mask[row].length();
if (length != this.mask[row].length) {
throw new IllegalArgumentException("Lengths of each string should be equal");
}
for (int column = 0; column < length; column++) {
char character = mask[row].charAt(column);
if (character == '0') {
this.mask[row][column] = false;
} else if (character == '1') {
this.mask[row][column] = true;
} else {
throw new IllegalArgumentException("Strings may only contain '0' and '1'");
}
}
}
}
public int getWidth() {
return mask.length == 0 ? 0 : mask[0].length;
}
public int getHeight() {
return mask.length;
}
public boolean isMasked(int x, int y) {
return mask[y][x];
}
}

View File

@ -0,0 +1,45 @@
package dev.ninjdai.werewolf.gui;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.inventory.Inventory;
import net.minestom.server.inventory.InventoryType;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import org.jetbrains.annotations.NotNull;
public class StackedGui extends Gui {
public StackedGui(@NotNull InventoryType inventoryType, @NotNull Component title) {
super(inventoryType, title);
assert inventoryType == InventoryType.CHEST_2_ROW || inventoryType == InventoryType.CHEST_3_ROW || inventoryType == InventoryType.CHEST_4_ROW || inventoryType == InventoryType.CHEST_5_ROW || inventoryType == InventoryType.CHEST_6_ROW;
fillBottomRom();
}
public StackedGui(@NotNull InventoryType inventoryType, @NotNull Component title , @NotNull Inventory parent) {
super(inventoryType, title);
assert inventoryType == InventoryType.CHEST_2_ROW || inventoryType == InventoryType.CHEST_3_ROW || inventoryType == InventoryType.CHEST_4_ROW || inventoryType == InventoryType.CHEST_5_ROW || inventoryType == InventoryType.CHEST_6_ROW;
fillBottomRom();
addGuiItem(0, (this.getSize() / 9 - 1), new GuiItem(
ItemStack.of(Material.ARROW).withCustomName(Component.text("Back").decoration(TextDecoration.ITALIC, false)).withLore(parent.getTitle().decoration(TextDecoration.ITALIC, false)),
event -> {
event.setCancelled(true);
event.getPlayer().openInventory(parent);
}
));
}
public void fillBottomRom() {
fillBottomRom(GuiItems.GLASS_PANE_FILLER);
}
public void fillBottomRom(GuiItem item) {
int y = (this.getSize() / 9 - 1);
for (int x = 0; x < 9; x++) {
addGuiItem(x, y, item);
}
addGuiItem(4, this.getSize()/9 -1, GuiItems.CLOSE_INVENTORY);
}
}

View File

@ -0,0 +1,10 @@
package dev.ninjdai.werewolf.uhc;
import dev.ninjdai.werewolf.uhc.roles.Role;
import java.util.HashMap;
import java.util.Map;
public class UHCConfig {
public Map<Role, Integer> activatedRoles = new HashMap<>();
}

View File

@ -34,23 +34,16 @@ public class UHCPlayer extends CombatPlayerImpl {
sendPacket(new WorldBorderSizePacket(getInstance().getWorldBorder().diameter()));
setGameMode(GameMode.SURVIVAL);
setCanPickupItem(true);
setInvulnerable(false);
setSilent(false);
this.collidesWithEntities = true;
setInvisible(false);
setFlying(false);
} else {
spectating = true;
sendPacket(new WorldBorderSizePacket(0));
setGameMode(GameMode.ADVENTURE);
setGameMode(GameMode.SPECTATOR);
setCanPickupItem(false);
setInvulnerable(true);
setSilent(true);
this.collidesWithEntities = false;
setInvisible(true);
setFlying(true);
heal();
}
}
}

View File

@ -0,0 +1,9 @@
package dev.ninjdai.werewolf.uhc.effects.neutrals;
import dev.ninjdai.werewolf.uhc.effects.Effect;
import dev.ninjdai.werewolf.uhc.effects.EffectProperty;
import net.minestom.server.potion.Potion;
import net.minestom.server.potion.PotionEffect;
public class AssassinStrength extends Effect {
}

View File

@ -14,12 +14,15 @@ import org.jetbrains.annotations.NotNull;
import java.util.*;
public class Role {
public Role(String id, String name, List<ItemStack> roleItems, List<Effect> roleEffects, Set<RoleProperties> roleProperties) {
public Role(String id, Component name, List<ItemStack> roleItems, List<Effect> roleEffects, Set<RoleProperties> roleProperties, boolean isDuo, int roleMaxCount, Component description) {
this.id = id;
this.name = name;
this.roleItems = roleItems;
this.roleEffects = roleEffects;
this.roleProperties = roleProperties;
this.isDuo = isDuo;
this.roleMaxCount = roleMaxCount;
this.description = description;
roleMap.put(this.id, this);
}
@ -30,15 +33,22 @@ public class Role {
this.roleItems = builder.roleItems;
this.roleEffects = builder.roleEffects;
this.roleProperties = builder.roleProperties;
this.isDuo = builder.isDuo;
this.roleMaxCount = builder.roleMaxCount;
this.description = builder.description;
roleMap.put(this.id, this);
}
@Getter private String id;
@Getter private String name;
@Getter private Component name;
@Getter private List<ItemStack> roleItems;
@Getter private List<Effect> roleEffects;
@Getter private Set<RoleProperties> roleProperties;
@Getter private boolean isDuo;
@Getter private int roleMaxCount;
@Getter private Component description;
public void onPlayerDeath(@NotNull UHCPlayer player, TimedPlayerDeath timedPlayerDeath) {
player.sendMessage(Component.text(
@ -51,22 +61,40 @@ public class Role {
public static final Map<String, Role> roleMap = new HashMap<>();
public static class Builder {
String id = "DUMMY";
String name = "Dummy";
private String id = "DUMMY";
private Component name = Component.text("Dummy");
private List<ItemStack> roleItems = new ArrayList<>();
private List<Effect> roleEffects = new ArrayList<>();
Set<RoleProperties> roleProperties = new HashSet<>();
private Set<RoleProperties> roleProperties = new HashSet<>();
private boolean isDuo = false;
private int roleMaxCount = 1;
private Component description = Component.text("");
public Builder setId(String id) {
this.id = id;
return this;
}
public Builder setName(String name) {
public Builder setName(Component name) {
this.name = name;
return this;
}
public Builder setDescription(Component description) {
this.description = description;
return this;
}
public Builder setRoleDuo() {
this.isDuo = true;
return this;
}
public Builder setRoleMaxCount(int count) {
this.roleMaxCount = count;
return this;
}
public Builder setRoleItems(List<ItemStack> roleItems) {
this.roleItems = roleItems;
return this;

View File

@ -1,10 +1,34 @@
package dev.ninjdai.werewolf.uhc.roles;
import dev.ninjdai.werewolf.uhc.effects.neutrals.AssassinStrength;
import dev.ninjdai.werewolf.uhc.effects.werewolves.WerewolfStrength;
import net.kyori.adventure.text.Component;
import net.minestom.server.component.DataComponent;
import net.minestom.server.item.ItemComponent;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.Material;
import net.minestom.server.item.component.EnchantmentList;
import net.minestom.server.item.component.PotionContents;
import net.minestom.server.item.enchant.Enchantment;
import net.minestom.server.potion.PotionType;
public enum Roles {
WEREWOLF(new Role.Builder().setId("werewolf").setName("Werewolf").addRoleEffect(new WerewolfStrength()).build()),
VILLAGER(new Role.Builder().setId("villager").setName("Villager").build());
// Werewolves
WEREWOLF(new Role.Builder().setId("werewolf").setName(Component.text("Werewolf")).setRoleMaxCount(99).addRoleEffect(new WerewolfStrength()).build()),
// Villagers
ANCIENT(new Role.Builder().setId("ancient").setName(Component.text("Ancient")).build()),
VILLAGER(new Role.Builder().setId("villager").setName(Component.text("Villager")).setRoleMaxCount(99).build()),
SISTER(new Role.Builder().setId("sister").setName(Component.text("Sister")).setRoleDuo().build()),
WITCH(new Role.Builder().setId("witch").setName(Component.text("Witch")).addRoleItems(
ItemStack.of(Material.SPLASH_POTION, 2).with(ItemComponent.POTION_CONTENTS, new PotionContents(PotionType.STRONG_HEALING))
).build()),
// Neutrals
ASSASSIN(new Role.Builder().setId("assassin").setName(Component.text("Assassin")).addRoleEffect(new AssassinStrength()).build()),
CUPID(new Role.Builder().setId("cupid").setName(Component.text("Cupid")).addRoleItems(
ItemStack.of(Material.ENCHANTED_BOOK).with(ItemComponent.ENCHANTMENTS, EnchantmentList.EMPTY.with(Enchantment.PUNCH, 1)),
ItemStack.of(Material.ARROW, 64)
).build()),
;
private final Role role;
Roles(Role role) {

View File

@ -20,6 +20,9 @@ public class WinCondition {
}
return true;
}
case ONLY_PLAYER_REMAINING -> {
return players.size() == 1 && players.stream().findFirst().get().getUHCTeam() == team;
}
}
return false;
}
@ -45,5 +48,6 @@ public class WinCondition {
public enum WinConditionType {
ONLY_TEAM_REMAINING,
ONLY_PLAYER_REMAINING,
}
}