diff --git a/src/main/java/io/github/gaming32/bingo/BingoCommand.java b/src/main/java/io/github/gaming32/bingo/BingoCommand.java index eca28404..2ebe081e 100644 --- a/src/main/java/io/github/gaming32/bingo/BingoCommand.java +++ b/src/main/java/io/github/gaming32/bingo/BingoCommand.java @@ -133,6 +133,7 @@ public class BingoCommand { ); }; + private static final CommandSwitch ALLOW_NEVER_GOALS_IN_LOCKOUT = CommandSwitch.storeTrue("--allow-never-goals-in-lockout"); private static final CommandSwitch REQUIRE_CLIENT = CommandSwitch.storeTrue("--require-client"); private static final CommandSwitch CONTINUE_AFTER_WIN = CommandSwitch.storeTrue("--continue-after-win"); private static final CommandSwitch INCLUDE_INACTIVE_TEAMS = CommandSwitch.storeTrue("--include-inactive-teams"); @@ -415,6 +416,7 @@ public Component getDisplayName() { { final CommandNode startCommand = bingoCommand.getChild("start"); + ALLOW_NEVER_GOALS_IN_LOCKOUT.addTo(startCommand); REQUIRE_CLIENT.addTo(startCommand); CONTINUE_AFTER_WIN.addTo(startCommand); INCLUDE_INACTIVE_TEAMS.addTo(startCommand); @@ -462,6 +464,7 @@ private static int startGame(CommandContext context, int tea throw INVALID_SIZE.create(size, shape.getMinSize(), shape.getMaxSize()); } final var gamemode = GAMEMODE.get(context).value(); + final boolean allowNeverGoalsInLockout = ALLOW_NEVER_GOALS_IN_LOCKOUT.get(context); final boolean requireClient = REQUIRE_CLIENT.get(context); final boolean continueAfterWin = CONTINUE_AFTER_WIN.get(context); final boolean includeInactiveTeams = INCLUDE_INACTIVE_TEAMS.get(context); @@ -503,6 +506,7 @@ private static int startGame(CommandContext context, int tea gamemode::isGoalAllowed, requiredGoals, excludedTags, + allowNeverGoalsInLockout, requireClient, registries ); diff --git a/src/main/java/io/github/gaming32/bingo/client/BingoClient.java b/src/main/java/io/github/gaming32/bingo/client/BingoClient.java index bbca388d..3734d843 100644 --- a/src/main/java/io/github/gaming32/bingo/client/BingoClient.java +++ b/src/main/java/io/github/gaming32/bingo/client/BingoClient.java @@ -192,7 +192,7 @@ class TeamValue { int totalScore = 0; for (final BingoBoard.Teams state : clientGame.states()) { - if (state.any()) { + if (state.count() == 1) { totalScore++; teams[state.getFirstIndex()].score++; } @@ -287,20 +287,18 @@ public static void renderBingo(GuiGraphicsExtractor graphics, boolean mouseHover final boolean isGoalCompleted = state.and(clientTeam); final int slotX = slotPos.x() * 18 + 8; final int slotY = slotPos.y() * 18 + 18; + int incompleteColor = Objects.requireNonNullElse(goal.specialType().incompleteColor, 0); - final Integer color = switch (clientGame.renderMode()) { - case FANCY -> isGoalCompleted ? Integer.valueOf(0x55ff55) : goal.specialType().incompleteColor; - case ALL_TEAMS -> { - if (!state.any()) { - yield null; - } - final BingoBoard.Teams team = isGoalCompleted ? clientTeam : state; - final Integer maybeColor = clientGame.teams()[team.getFirstIndex()].getColor().getColor(); - yield maybeColor != null ? maybeColor : 0x55ff55; - } + final int[] colors = switch (clientGame.renderMode()) { + case FANCY -> new int[]{(isGoalCompleted ? 0x55ff55 : incompleteColor)}; + case ALL_TEAMS -> state.stream().map((team) -> clientGame.teams()[team].getColor().getColor()).toArray(); }; - if (color != null) { - graphics.fill(slotX, slotY, slotX + 16, slotY + 16, 0xA0000000 | color); + for (int i = 0; i < colors.length; ++i) { + int color = colors[i]; + int start = 16 * i / colors.length; + int end = 16 * (i + 1) / colors.length; + int base = (colors.length == 1) ? 0xA0000000 : 0x50000000; + graphics.fill(slotX + start, slotY, slotX + end, slotY + 16, base | color); } Integer manualHighlight = clientGame.manualHighlights()[goalIndex]; diff --git a/src/main/java/io/github/gaming32/bingo/data/BingoTags.java b/src/main/java/io/github/gaming32/bingo/data/BingoTags.java index 4b1662fa..b8b85bdd 100644 --- a/src/main/java/io/github/gaming32/bingo/data/BingoTags.java +++ b/src/main/java/io/github/gaming32/bingo/data/BingoTags.java @@ -12,6 +12,7 @@ public final class BingoTags { public static final ResourceKey END = createKey("end"); public static final ResourceKey FINISH = createKey("finish"); public static final ResourceKey ITEM = createKey("item"); + public static final ResourceKey LOCKOUT_INFLICTABLE = createKey("lockout_inflictable"); public static final ResourceKey NETHER = createKey("nether"); public static final ResourceKey NEVER = createKey("never"); public static final ResourceKey OCEAN = createKey("ocean"); @@ -36,6 +37,7 @@ public static void bootstrap(BootstrapContext context) { .build() ); context.register(ITEM, BingoTag.builder().difficultyMax(25, 25, 20, 20, 20).build()); + context.register(LOCKOUT_INFLICTABLE, BingoTag.builder().markerTag().build()); context.register(NETHER, BingoTag.builder().difficultyMax(0, 2, 5, 10, 15).build()); context.register(NEVER, BingoTag.builder() .difficultyMax(3, 3, 3, 2, 1) diff --git a/src/main/java/io/github/gaming32/bingo/data/goal/GoalHolder.java b/src/main/java/io/github/gaming32/bingo/data/goal/GoalHolder.java index 6f0bd57c..4dbe08d3 100644 --- a/src/main/java/io/github/gaming32/bingo/data/goal/GoalHolder.java +++ b/src/main/java/io/github/gaming32/bingo/data/goal/GoalHolder.java @@ -1,5 +1,6 @@ package io.github.gaming32.bingo.data.goal; +import io.github.gaming32.bingo.data.BingoTags; import io.github.gaming32.bingo.game.ActiveGoal; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; @@ -26,6 +27,7 @@ public ActiveGoal build(RandomSource rand) { goal.buildRequiredCount(context), Optional.of(goal.getDifficulty()), goal.getRequirements(), + goal.getTags().stream().anyMatch(holder -> holder.is(BingoTags.LOCKOUT_INFLICTABLE)), goal.getSpecialType(), goal.getProgress() ); diff --git a/src/main/java/io/github/gaming32/bingo/game/ActiveGoal.java b/src/main/java/io/github/gaming32/bingo/game/ActiveGoal.java index 51ac60a0..7f670a07 100644 --- a/src/main/java/io/github/gaming32/bingo/game/ActiveGoal.java +++ b/src/main/java/io/github/gaming32/bingo/game/ActiveGoal.java @@ -45,6 +45,7 @@ public record ActiveGoal( int requiredCount, Optional> difficulty, AdvancementRequirements requirements, + Boolean isLockoutInflictable, BingoTag.SpecialType specialType, ProgressTracker progress ) { @@ -61,6 +62,7 @@ public record ActiveGoal( .fieldOf("difficulty") .forGetter(ActiveGoal::difficulty), AdvancementRequirements.CODEC.fieldOf("requirements").forGetter(ActiveGoal::requirements), + Codec.BOOL.fieldOf("isLockoutInflictable").forGetter(ActiveGoal::isLockoutInflictable), BingoTag.SpecialType.CODEC .optionalFieldOf("special_type", BingoTag.SpecialType.NONE) .forGetter(ActiveGoal::specialType), @@ -75,6 +77,7 @@ public record ActiveGoal( ComponentSerialization.TRUSTED_OPTIONAL_STREAM_CODEC, ActiveGoal::tooltip, Identifier.STREAM_CODEC.apply(ByteBufCodecs::optional), ActiveGoal::tooltipIcon, GoalIcon.STREAM_CODEC, ActiveGoal::icon, + ByteBufCodecs.BOOL, ActiveGoal::isLockoutInflictable, BingoTag.SpecialType.STREAM_CODEC, ActiveGoal::specialType, ActiveGoal::forClient ); @@ -85,12 +88,13 @@ public static ActiveGoal forClient( Optional tooltip, Optional tooltipIcon, GoalIcon icon, + boolean isLockoutInflictable, BingoTag.SpecialType specialType ) { return new ActiveGoal( id, name, tooltip, tooltipIcon, icon, Map.of(), 1, Optional.empty(), AdvancementRequirements.EMPTY, - specialType, + isLockoutInflictable, specialType, EmptyProgressTracker.INSTANCE ); } diff --git a/src/main/java/io/github/gaming32/bingo/game/BingoBoard.java b/src/main/java/io/github/gaming32/bingo/game/BingoBoard.java index 0efc0237..c15d44ec 100644 --- a/src/main/java/io/github/gaming32/bingo/game/BingoBoard.java +++ b/src/main/java/io/github/gaming32/bingo/game/BingoBoard.java @@ -46,7 +46,7 @@ import java.util.OptionalInt; import java.util.Queue; import java.util.Set; -import java.util.function.Predicate; +import java.util.function.BiPredicate; import java.util.stream.IntStream; public class BingoBoard { @@ -107,9 +107,10 @@ public static BingoBoard generate( int difficulty, int teamCount, RandomSource rand, - Predicate isAllowedGoal, + BiPredicate isAllowedGoal, Collection requiredGoals, HolderSet excludedTags, + boolean allowNeverGoalsInLockout, boolean allowsClientRequired, HolderLookup.Provider registries ) { @@ -124,6 +125,7 @@ public static BingoBoard generate( isAllowedGoal, requiredGoals, excludedTags, + allowNeverGoalsInLockout, allowsClientRequired ); for (int i = 0; i < goalCount; i++) { @@ -149,9 +151,10 @@ public static GoalHolder[] generateGoals( int size, int difficulty, RandomSource rand, - Predicate isAllowedGoal, + BiPredicate isAllowedGoal, Collection requiredGoals, HolderSet excludedTags, + boolean allowNeverGoalsInLockout, boolean allowsClientRequired ) { final Queue requiredGoalQueue = new ArrayDeque<>(requiredGoals); @@ -211,7 +214,7 @@ public static GoalHolder[] generateGoals( final GoalHolder goalCandidate = possibleGoals.get(rand.nextInt(possibleGoals.size())); - if (!isAllowedGoal.test(goalCandidate)) { + if (!isAllowedGoal.test(goalCandidate, allowNeverGoalsInLockout)) { continue; } diff --git a/src/main/java/io/github/gaming32/bingo/game/BingoGame.java b/src/main/java/io/github/gaming32/bingo/game/BingoGame.java index 5239a703..2df995a3 100644 --- a/src/main/java/io/github/gaming32/bingo/game/BingoGame.java +++ b/src/main/java/io/github/gaming32/bingo/game/BingoGame.java @@ -550,7 +550,7 @@ private void updateTeamBoard(ServerPlayer player, ActiveGoal goal, boolean revok board[index] = isLoss ? board[index].andNot(team) : board[index].or(team); MinecraftServer server = player.level().getServer(); notifyTeam(player, team, goal, server.getPlayerList(), index, isLoss); - if (!isLoss) { + if (!isLoss || isNever) { checkForWin(server.getPlayerList()); } } @@ -631,14 +631,15 @@ private void notifyTeam( teamComponent = teamComponent.copy().withStyle(playerTeam.getColor()); } final Component lockoutMessage = Bingo.translatable( - "bingo.goal_lost.lockout", + isLoss ? "bingo.goal_lost_other.lockout" : "bingo.goal_lost.lockout", teamComponent, goal.name().copy().withStyle(ChatFormatting.GOLD) ); for (final ServerPlayer player : playerList.getPlayers()) { if (player.isAlliedTo(playerTeam)) continue; if (announceGoal) { player.connection.send(new ClientboundSoundEntityPacket( - SoundEvents.RESPAWN_ANCHOR_DEPLETE, SoundSource.MASTER, + isLoss ? Holder.direct(SoundEvents.NOTE_BLOCK_CHIME.value()) : SoundEvents.RESPAWN_ANCHOR_DEPLETE, + SoundSource.MASTER, player, 0.5f, 1f, player.getRandom().nextLong() )); player.sendSystemMessage(lockoutMessage); diff --git a/src/main/java/io/github/gaming32/bingo/game/mode/BingoGameMode.java b/src/main/java/io/github/gaming32/bingo/game/mode/BingoGameMode.java index 5b786676..c02e17a1 100644 --- a/src/main/java/io/github/gaming32/bingo/game/mode/BingoGameMode.java +++ b/src/main/java/io/github/gaming32/bingo/game/mode/BingoGameMode.java @@ -37,7 +37,7 @@ default CommandSyntaxException checkAllowedConfig(GameConfig config) { boolean canGetGoal(BingoBoard board, int index, BingoBoard.Teams team, boolean isNever); - default boolean isGoalAllowed(GoalHolder goal) { + default boolean isGoalAllowed(GoalHolder goal, boolean allowNeverGoalsInLockout) { return true; } diff --git a/src/main/java/io/github/gaming32/bingo/game/mode/BlackoutGameMode.java b/src/main/java/io/github/gaming32/bingo/game/mode/BlackoutGameMode.java index e5effc3c..a35e8b7d 100644 --- a/src/main/java/io/github/gaming32/bingo/game/mode/BlackoutGameMode.java +++ b/src/main/java/io/github/gaming32/bingo/game/mode/BlackoutGameMode.java @@ -33,7 +33,7 @@ public boolean canGetGoal(BingoBoard board, int index, BingoBoard.Teams team, bo } @Override - public boolean isGoalAllowed(GoalHolder goal) { + public boolean isGoalAllowed(GoalHolder goal, boolean allowNeverGoalsInLockout) { return goal.goal().getTags().stream().allMatch(g -> g.value().specialType() == BingoTag.SpecialType.NONE); } } diff --git a/src/main/java/io/github/gaming32/bingo/game/mode/LockoutGameMode.java b/src/main/java/io/github/gaming32/bingo/game/mode/LockoutGameMode.java index f1deb8d6..efc7b624 100644 --- a/src/main/java/io/github/gaming32/bingo/game/mode/LockoutGameMode.java +++ b/src/main/java/io/github/gaming32/bingo/game/mode/LockoutGameMode.java @@ -5,6 +5,7 @@ import io.github.gaming32.bingo.Bingo; import io.github.gaming32.bingo.data.BingoTag; import io.github.gaming32.bingo.data.goal.GoalHolder; +import io.github.gaming32.bingo.game.ActiveGoal; import io.github.gaming32.bingo.game.BingoBoard; import io.github.gaming32.bingo.game.BingoGame; import net.minecraft.ChatFormatting; @@ -60,16 +61,22 @@ class TeamValue { } int totalHeld = 0; - for (final BingoBoard.Teams state : board.getStates()) { - if (state.any()) { + boolean nonStalemateGoalsLeft = false; + + for (int goalNumber = 0; goalNumber < board.getGoals().length; goalNumber++) { + BingoBoard.Teams state = board.getStates()[goalNumber]; + if (state.count() == 1) { totalHeld++; teams[state.getFirstIndex()].goalsHeld++; + } else if (!nonStalemateGoalsLeft) { + ActiveGoal activeGoal = board.getGoals()[goalNumber]; + nonStalemateGoalsLeft = activeGoal.specialType() != BingoTag.SpecialType.NEVER || activeGoal.isLockoutInflictable(); } } Arrays.sort(teams, Comparator.comparing(v -> -v.goalsHeld)); // Sort in reverse - final int totalGoals = board.getShape().getGoalCount(board.getSize()); + final int totalGoals = nonStalemateGoalsLeft ? board.getShape().getGoalCount(board.getSize()) : totalHeld; if (totalGoals - totalHeld < teams[0].goalsHeld - teams[1].goalsHeld) { return teams[0].team; } @@ -90,12 +97,24 @@ class TeamValue { @Override public boolean canGetGoal(BingoBoard board, int index, BingoBoard.Teams team, boolean isNever) { - return !board.getStates()[index].any(); + if (!isNever) { + return !board.getStates()[index].any(); + } else { + return board.getStates()[index].count() > 1 && board.getStates()[index].and(team); + } } @Override - public boolean isGoalAllowed(GoalHolder goal) { - return goal.goal().getTags().stream().allMatch(g -> g.value().specialType() == BingoTag.SpecialType.NONE); + public boolean isGoalAllowed(GoalHolder goal, boolean allowNeverGoalsInLockout) { + return goal.goal().getTags().stream().allMatch((g) -> { + if (g.value().specialType() == BingoTag.SpecialType.NONE) { + return true; + } + if (g.value().specialType() == BingoTag.SpecialType.NEVER) { + return allowNeverGoalsInLockout; + } + return false; + }); } @Override diff --git a/src/main/resources/assets/bingo/lang/en_us.json b/src/main/resources/assets/bingo/lang/en_us.json index f37a53fd..353e1141 100644 --- a/src/main/resources/assets/bingo/lang/en_us.json +++ b/src/main/resources/assets/bingo/lang/en_us.json @@ -277,6 +277,7 @@ "bingo.goal_lost": "%s lost the goal %s for your team.", "bingo.goal_lost.single": "You lost the goal %s.", "bingo.goal_lost.lockout": "%s got the goal %s.", + "bingo.goal_lost_other.lockout": "%s lost the goal %s.", "bingo.duplicate_teams": "Cannot start a bingo with the same team twice (%s)!", "bingo.unknown_difficulty": "Unknown difficulty %s", "bingo.unknown_goal": "Unknown goal %s", diff --git a/src/main/resources/assets/bingo/lang/ru_ru.json b/src/main/resources/assets/bingo/lang/ru_ru.json index a2cff634..c53f523d 100644 --- a/src/main/resources/assets/bingo/lang/ru_ru.json +++ b/src/main/resources/assets/bingo/lang/ru_ru.json @@ -250,6 +250,7 @@ "bingo.goal_obtained": "%s получил цель %s для вашей команды.", "bingo.goal_lost": "%s потерял цель %s для вашей команды.", "bingo.goal_lost.lockout": "%s получил цель %s.", + "bingo.goal_lost_other.lockout": "%s потерял цель %s.", "bingo.duplicate_teams": "Нельзя дважды начинать игру в бинго с одной и той же командой (%s)!", "bingo.unknown_difficulty": "Неизвестная сложность %s", "bingo.unknown_goal": "Неизвестная цель %s",