diff --git a/src/main/java/com/lovetropics/minigames/common/content/connect4/ConnectFourBehavior.java b/src/main/java/com/lovetropics/minigames/common/content/connect4/ConnectFourBehavior.java index 69cf8368..b8255e2a 100644 --- a/src/main/java/com/lovetropics/minigames/common/content/connect4/ConnectFourBehavior.java +++ b/src/main/java/com/lovetropics/minigames/common/content/connect4/ConnectFourBehavior.java @@ -1,6 +1,7 @@ package com.lovetropics.minigames.common.content.connect4; import com.lovetropics.lib.BlockBox; +import com.lovetropics.lib.codec.MoreCodecs; import com.lovetropics.minigames.common.content.MinigameTexts; import com.lovetropics.minigames.common.core.game.GameException; import com.lovetropics.minigames.common.core.game.GameStopReason; @@ -39,8 +40,11 @@ public class ConnectFourBehavior implements IGameBehavior { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(in -> in.group( Codec.unboundedMap(GameTeamKey.CODEC, GameBlock.CODEC.codec()).fieldOf("team_blocks").forGetter(c -> c.teamBlocks), Codec.STRING.fieldOf("placing_region").forGetter(c -> c.placingRegionKey), - BlockState.CODEC.fieldOf("separator").forGetter(c -> c.separator), - BlockState.CODEC.fieldOf("blocker").forGetter(c -> c.blocker) + MoreCodecs.BLOCK_STATE.fieldOf("separator").forGetter(c -> c.separator), + MoreCodecs.BLOCK_STATE.fieldOf("blocker").forGetter(c -> c.blocker), + Codec.INT.fieldOf("grid_width").forGetter(c -> c.width), + Codec.INT.fieldOf("grid_height").forGetter(c -> c.height), + Codec.INT.optionalFieldOf("connect", 4).forGetter(c -> c.connectAmount) ).apply(in, ConnectFourBehavior::new)); private final Map teamBlocks; @@ -48,11 +52,16 @@ public class ConnectFourBehavior implements IGameBehavior { private final BlockState separator; private final BlockState blocker; - public ConnectFourBehavior(Map teamBlocks, String placingRegionKey, BlockState separator, BlockState blocker) { + private final int width, height, connectAmount; + + public ConnectFourBehavior(Map teamBlocks, String placingRegionKey, BlockState separator, BlockState blocker, int width, int height, int connectAmount) { this.teamBlocks = teamBlocks; this.placingRegionKey = placingRegionKey; this.separator = separator; this.blocker = blocker; + this.width = width; + this.height = height; + this.connectAmount = connectAmount; } private IGamePhase game; @@ -74,7 +83,7 @@ public void register(IGamePhase game, EventRegistrar events) throws GameExceptio placingRegion = game.mapRegions().getOrThrow(placingRegionKey); teams = game.instanceState().getOrThrow(TeamState.KEY); - pieces = new GameTeamKey[7][6]; + pieces = new GameTeamKey[width][height]; placedPieces = 0; events.listen(GamePhaseEvents.START, this::onStart); @@ -161,7 +170,7 @@ private void onBlockLanded(Level level, BlockPos pos, BlockState state) { game.schedule(1.5f, () -> game.allPlayers().sendMessage(MinigameTexts.TEAM_WON.apply(teamConfig.styledName()).withStyle(ChatFormatting.GREEN), true)); game.schedule(5, () -> game.requestStop(GameStopReason.finished())); } else { - if (placedPieces == 7 * 6) { + if (placedPieces == width * height) { game.invoker(GameLogicEvents.GAME_OVER).onGameOver(); game.schedule(1.5f, () -> game.allPlayers().sendMessage(MinigameTexts.NOBODY_WON, true)); @@ -190,16 +199,16 @@ private boolean checkWin(int x, int y, GameTeamKey team) { return true; } - for (int offset = 0; offset < 4; ++offset) { - if (checkLine(x - 3 + offset, y, 1, 0, team)) { // horizontal + for (int offset = 0; offset < connectAmount; offset++) { + if (checkLine(x - (connectAmount - 1) + offset, y, 1, 0, team)) { // horizontal return true; } - if (checkLine(x - 3 + offset, y + 3 - offset, 1, -1, team)) { // leading diagonal + if (checkLine(x - (connectAmount + 1) + offset, y + (connectAmount - 1) - offset, 1, -1, team)) { // leading diagonal return true; } - if (checkLine(x - 3 + offset, y - 3 + offset, 1, 1, team)) { // trailing diagonal + if (checkLine(x - (connectAmount + 1) + offset, y - (connectAmount + 1) + offset, 1, 1, team)) { // trailing diagonal return true; } } @@ -209,7 +218,7 @@ private boolean checkWin(int x, int y, GameTeamKey team) { private boolean checkLine(int xs, int ys, int dx, int dy, GameTeamKey team) { - for (int i = 0; i < 4; i++) { + for (int i = 0; i < connectAmount; i++) { int x = xs + (dx * i); int y = ys + (dy * i); diff --git a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/CraftingBeeBehavior.java b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/CraftingBeeBehavior.java index 8e7da80f..1c955aac 100644 --- a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/CraftingBeeBehavior.java +++ b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/CraftingBeeBehavior.java @@ -14,6 +14,7 @@ import com.lovetropics.minigames.common.core.game.behavior.event.GamePhaseEvents; import com.lovetropics.minigames.common.core.game.behavior.event.GamePlayerEvents; import com.lovetropics.minigames.common.core.game.client_state.GameClientState; +import com.lovetropics.minigames.common.core.game.client_state.GameClientStateTypes; import com.lovetropics.minigames.common.core.game.client_state.instance.CraftingBeeCraftsClientState; import com.lovetropics.minigames.common.core.game.player.PlayerSet; import com.lovetropics.minigames.common.core.game.state.statistics.StatisticKey; @@ -48,7 +49,7 @@ public class CraftingBeeBehavior implements IGameBehavior { public static final MapCodec CODEC = RecordCodecBuilder.mapCodec(in -> in.group( - RecipeSelector.CODEC.codec().listOf().fieldOf("selectors").forGetter(c -> c.selectors), + RecipeSelector.CODEC.listOf().fieldOf("selectors").forGetter(c -> c.selectors), IngredientDecomposer.CODEC.codec().listOf().fieldOf("decomposers").forGetter(c -> c.decomposers), Codec.INT.optionalFieldOf("hints_per_player", 3).forGetter(c -> c.allowedHints) ).apply(in, CraftingBeeBehavior::new)); @@ -81,6 +82,9 @@ public void register(IGamePhase game, EventRegistrar events) throws GameExceptio events.listen(GamePhaseEvents.START, this::start); events.listen(GamePlayerEvents.CRAFT, this::onCraft); events.listen(GamePlayerEvents.USE_BLOCK, this::useBlock); + + events.listen(GamePhaseEvents.STOP, reason -> GameClientState.removeFromPlayers(GameClientStateTypes.CRAFTING_BEE_CRAFTS.get(), game.allPlayers())); + events.listen(GamePlayerEvents.REMOVE, player -> GameClientState.removeFromPlayer(GameClientStateTypes.CRAFTING_BEE_CRAFTS.get(), player)); } private void start() { diff --git a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/RecipeSelector.java b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/RecipeSelector.java index 64842c7c..a611af8a 100644 --- a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/RecipeSelector.java +++ b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/RecipeSelector.java @@ -2,17 +2,24 @@ import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; +import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import net.minecraft.Util; +import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; +import net.minecraft.tags.TagKey; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.crafting.RecipeHolder; +import net.minecraft.world.item.crafting.RecipeType; import java.util.List; +import java.util.Optional; public interface RecipeSelector { - BiMap> TYPES = ImmutableBiMap.of("from_list", FromList.CODEC); - MapCodec CODEC = Codec.STRING.dispatchMap(s -> TYPES.inverse().get(s.getType()), TYPES::get); + BiMap> TYPES = ImmutableBiMap.of("from_list", FromList.CODEC, "one_of", OneOf.CODEC, "from_item_tag", FromItemTag.CODEC); + Codec CODEC = Codec.STRING.dispatch(s -> TYPES.inverse().get(s.getType()), TYPES::get); SelectedRecipe select(ServerLevel level); @@ -24,9 +31,58 @@ record FromList(List recipes) implements RecipeSelector { @Override public SelectedRecipe select(ServerLevel level) { - var key = Util.getRandom(recipes, level.getRandom()); - var recipe = level.getRecipeManager().byKey(key).orElseThrow(() -> new NullPointerException("Recipe " + key + " doesn't exist")); - return new SelectedRecipe(recipe); + Optional> recipe = Optional.empty(); + while (recipe.isEmpty()) { + var key = Util.getRandom(recipes, level.getRandom()); + recipe = level.getRecipeManager().byKey(key); + if (recipe.isEmpty()) { + LogUtils.getLogger().error("Recipe '{}' doesn't exist", key); + } + } + return new SelectedRecipe(recipe.get()); + } + + @Override + public MapCodec getType() { + return CODEC; + } + } + + record OneOf(List selectors) implements RecipeSelector { + public static final MapCodec CODEC = MapCodec.assumeMapUnsafe(Codec.lazyInitialized(() -> RecipeSelector.CODEC.listOf().fieldOf("selectors") + .xmap(OneOf::new, OneOf::selectors).codec())); + @Override + public SelectedRecipe select(ServerLevel level) { + return Util.getRandom(selectors, level.getRandom()).select(level); + } + + @Override + public MapCodec getType() { + return CODEC; + } + } + + class FromItemTag implements RecipeSelector { + public static final MapCodec CODEC = TagKey.hashedCodec(Registries.ITEM).fieldOf("tag") + .xmap(FromItemTag::new, s -> s.tag); + + private final TagKey tag; + + private List cache; + + public FromItemTag(TagKey tag) { + this.tag = tag; + } + + @Override + public SelectedRecipe select(ServerLevel level) { + if (cache == null) { + cache = level.getRecipeManager().getAllRecipesFor(RecipeType.CRAFTING) + .stream().filter(h -> h.value().getResultItem(level.registryAccess()).is(tag) && h.id().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) + .map(SelectedRecipe::new) + .toList(); + } + return Util.getRandom(cache, level.getRandom()); } @Override diff --git a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/ingredient/SimpleTagToItemDecomposer.java b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/ingredient/SimpleTagToItemDecomposer.java index ac48ed9d..a5856b4e 100644 --- a/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/ingredient/SimpleTagToItemDecomposer.java +++ b/src/main/java/com/lovetropics/minigames/common/content/crafting_bee/ingredient/SimpleTagToItemDecomposer.java @@ -14,7 +14,7 @@ public record SimpleTagToItemDecomposer() implements IngredientDecomposer { public @Nullable List decompose(Ingredient ingredient) { // This is a "hack". Neo will sometimes replace a vanilla recipe with a difference ingredient (#chests - #chests/trapped) // we just resolve it and return the first item - if (ingredient.getCustomIngredient() instanceof DifferenceIngredient) { + if (ingredient.getCustomIngredient() != null) { return List.of(Ingredient.of(ingredient.getItems()[0])); } else if (ingredient.getValues().length == 1 && ingredient.getValues()[0] instanceof Ingredient.TagValue) { var items = ingredient.getValues()[0].getItems(); diff --git a/src/main/java/com/lovetropics/minigames/common/core/game/behavior/instances/SetGameClientStateBehavior.java b/src/main/java/com/lovetropics/minigames/common/core/game/behavior/instances/SetGameClientStateBehavior.java index f644f732..910ba084 100644 --- a/src/main/java/com/lovetropics/minigames/common/core/game/behavior/instances/SetGameClientStateBehavior.java +++ b/src/main/java/com/lovetropics/minigames/common/core/game/behavior/instances/SetGameClientStateBehavior.java @@ -3,6 +3,7 @@ import com.lovetropics.minigames.common.core.game.IGamePhase; import com.lovetropics.minigames.common.core.game.behavior.IGameBehavior; import com.lovetropics.minigames.common.core.game.behavior.event.EventRegistrar; +import com.lovetropics.minigames.common.core.game.behavior.event.GamePhaseEvents; import com.lovetropics.minigames.common.core.game.client_state.GameClientState; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; @@ -15,5 +16,6 @@ public record SetGameClientStateBehavior(GameClientState state) implements IGame @Override public void register(IGamePhase game, EventRegistrar events) { GameClientState.applyGlobally(state, events); + events.listen(GamePhaseEvents.STOP, reason -> GameClientState.removeFromPlayers(state.getType(), game.allPlayers())); } }