From 3395b3bcd528abbced167fa846d35858f3aaaf5f Mon Sep 17 00:00:00 2001 From: Quackster Date: Wed, 23 Apr 2025 18:40:07 +1000 Subject: [PATCH] feat: Poker by wackfx --- .../game/games/gamehalls/GameBattleShip.java | 6 +- .../game/games/gamehalls/GameChess.java | 6 +- .../game/games/gamehalls/GamePoker.java | 282 +++++++++++++++++- .../game/games/gamehalls/GameTicTacToe.java | 3 +- .../game/games/gamehalls/GamehallGame.java | 8 +- .../gamehalls/utils/GamePokerCombination.java | 107 +++++++ .../gamehalls/utils/GamePokerOptions.java | 265 ++++++++++++++++ .../outgoing/rooms/games/ITEMMSG.java | 27 +- .../util/config/writer/GameConfigWriter.java | 28 ++ 9 files changed, 704 insertions(+), 28 deletions(-) create mode 100644 Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerCombination.java create mode 100644 Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerOptions.java diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameBattleShip.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameBattleShip.java index b3046e9..1188218 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameBattleShip.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameBattleShip.java @@ -51,6 +51,7 @@ public class GameBattleShip extends GamehallGame { this.gameEnded = false; } + /* @Override public void joinGame(Player player) { if (!this.gameStarted) { @@ -78,7 +79,8 @@ public class GameBattleShip extends GamehallGame { this.sendToEveryone(new ITEMMSG(new String[]{this.getGameId(), "OPPONENTS", String.join(Character.toString((char) 13), opponentData)})); this.sendMarkedMap(); } - +*/ + /* @Override public void leaveGame(Player player) { if (this.nextTurn == getPlayerNum(player)) { @@ -92,6 +94,8 @@ public class GameBattleShip extends GamehallGame { } } + */ + @Override public void handleCommand(Player player, Room room, Item item, String command, String[] args) { GameTrigger trigger = (GameTrigger) item.getDefinition().getInteractionType().getTrigger(); diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameChess.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameChess.java index 00ddd10..09420dd 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameChess.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameChess.java @@ -54,14 +54,12 @@ public class GameChess extends GamehallGame { this.board = null; } - @Override - public void joinGame(Player player) { } - + /* @Override public void leaveGame(Player player) { this.playerSides.remove(player); } - +*/ @Override public void handleCommand(Player player, Room room, Item item, String command, String[] args) { GameTrigger trigger = (GameTrigger) item.getDefinition().getInteractionType().getTrigger(); diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamePoker.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamePoker.java index be52922..06153da 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamePoker.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamePoker.java @@ -1,39 +1,293 @@ package org.alexdev.havana.game.games.gamehalls; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.alexdev.havana.game.games.gamehalls.utils.GamePokerCombination; +import org.alexdev.havana.game.games.gamehalls.utils.GamePokerOptions; +import org.alexdev.havana.game.games.triggers.GameTrigger; import org.alexdev.havana.game.item.Item; import org.alexdev.havana.game.player.Player; import org.alexdev.havana.game.room.Room; -import org.alexdev.havana.game.games.triggers.GameTrigger; - -import java.util.List; +import org.alexdev.havana.messages.outgoing.rooms.games.ITEMMSG; +import org.alexdev.havana.messages.types.MessageComposer; public class GamePoker extends GamehallGame { public GamePoker(List kvp) { super(kvp); } - @Override - public void gameStart() { } + // Players that played at least one round (not on open screen) + private List playersInGame; + + // Players that are currently in round + private List playersPlaying; + + // Changes and cards for this round + private List deck; + private Map playerChanges; + private Map> playerCards; + + private GamePokerOptions options; + @Override - public void gameStop() { } + public void gameStart() { + this.deck = new ArrayList<>(); + this.playerCards = new HashMap<>(); + this.playerChanges = new HashMap<>(); + this.playersInGame = new ArrayList<>(); + this.playersPlaying = new ArrayList<>(); + this.options = new GamePokerOptions(this); + } @Override - public void joinGame(Player p) { } - - @Override - public void leaveGame(Player player) { } + public void gameStop() { + this.clearRound(); + this.playersInGame.clear(); + } @Override public void handleCommand(Player player, Room room, Item item, String command, String[] args) { GameTrigger trigger = (GameTrigger) item.getDefinition().getInteractionType().getTrigger(); - if (command.equals("CLOSE")) { - trigger.onEntityLeave(player, player.getRoomUser(), item); - return; + switch (command) { + case "CLOSE" -> { + trigger.onEntityLeave(player, player.getRoomUser(), item); + this.getDisconnectedPlayers().stream().forEach(this::disconnectPlayer); + if (this.isRoundRunning() && this.hasEveryoneChanged()) + this.endRound(); + return; + } + case "OPEN" -> { + this.getDisconnectedPlayers().stream().forEach(this::disconnectPlayer); + this.broadcastOpponents(player); + return; + } + case "STARTOVER" -> { + if (!this.isRoundRunning()) { + this.clearRound(); + this.deck.addAll(GamePokerCombination.getDeck()); + } + this.addPlayerToGame(player); + this.addPlayerToRound(player); + this.giveCardsToPlayer(player); + this.broadcastOpponents(); + return; + } + case "CHANGE" -> { + this.getDisconnectedPlayers().stream().forEach(this::disconnectPlayer); + this.changeCards(player, args); + this.broadcastOpponents(); + if (this.isRoundRunning() && this.hasEveryoneChanged()) + this.endRound(); + return; + } } } + private void addPlayerToGame(Player player) { + if (this.playersInGame.contains(player)) + return; + + this.playersInGame.add(player); + this.broadcastOpponents(); + } + + private void addPlayerToRound(Player player) { + this.playersPlaying.add(player); + + // Update player with latest state + this.playerChanges.keySet().stream() + .map(key -> this.formatPlayerChangedUpdate(key)) + .forEach(entry -> player.send(new ITEMMSG(entry))); + + this.options.onEntry(player); + } + + private void giveCardsToPlayer(Player player) { + List cards = IntStream.rangeClosed(0, 4) + .mapToObj(i -> this.deck.remove(ThreadLocalRandom.current().nextInt(this.deck.size()))) + .toList(); + this.playerCards.put(player, cards); + this.playerChanges.remove(player); + + player.send(new ITEMMSG(new String[]{this.getGameId(), "YOURCARDS 0/" + this.formatRevealCardsUpdate(player)})); + } + + private void changeCards(Player player, String[] sIndexes) { + if (this.playerChanges.containsKey(player)) + return; + + List playerHand = this.playerCards.get(player); + List cardsToChange = Arrays.asList(sIndexes) + .stream() + .filter(predicate -> !predicate.isBlank()) + .map(Integer::parseInt) + .filter(index -> index >= 0 && index < playerHand.size()) + .sorted().collect(Collectors.toList()); + + List changeHand = playerHand.stream() + .map(card -> { + int index = playerHand.indexOf(card); + if (cardsToChange.contains(index)) + return this.deck.remove(ThreadLocalRandom.current().nextInt(this.deck.size())); + else + return card; + }) + .collect(Collectors.toList()); + + String[] cardsChanged = cardsToChange.stream().map(String::valueOf).toArray(String[]::new); + + this.playerCards.put(player, changeHand); + this.playerChanges.put(player, cardsChanged); + + // Send change event & reveal new cards to player + this.sentToPlayingOpponents(player, new ITEMMSG( + this.formatPlayerChangedUpdate(player) + )); + + player.send(new ITEMMSG( + String.join(Character.toString((char) 13), new String[]{ + this.getGameId(), + "YOURCARDS 0/" + this.formatRevealCardsUpdate(player) + })) + ); + } + + private String formatPlayerChangedUpdate(Player player) { + if (!this.playerChanges.containsKey(player)) + return ""; + String[] cardsChanged = this.playerChanges.get(player); + String change = player.getDetails().getName() + "/" + String.join(" ", cardsChanged); + + return String.join(Character.toString((char) 13), new String[]{ + this.getGameId(), + "CHANGED", + change + }); + } + + private String formatRevealCardsUpdate(Player player) { + return String.join("/", new String[] { + String.join(",", this.playerChanges.containsKey(player) ? "1" : "0", Integer.toString(this.playerChanges.getOrDefault(player, new String[]{}).length)), + String.join("/", this.playerCards.get(player)) + }); + } + + private void disconnectPlayer(Player player) { + if (!this.playersInGame.contains(player)) + return; + + this.playersInGame.remove(player); + this.playersPlaying.remove(player); + this.playerChanges.remove(player); + this.playerCards.remove(player); + + ITEMMSG itemMessage = new ITEMMSG(String.join(Character.toString((char) 13), + this.getGameId(), + "OPPONENT_LOGOUT", + player.getDetails().getName() + )); + + for (Player p : this.playersInGame) + p.send(itemMessage); + } + + private void broadcastOpponents() { + for (Player player: this.playersInGame) { + this.broadcastOpponents(player); + } + } + + private void broadcastOpponents(Player player) { + List opponentData = new ArrayList<>(); + opponentData.add(player.getDetails().getName() + " " + Integer.toString(this.playerChanges.getOrDefault(player, new String[]{}).length)); + + for (Player p: this.playersInGame) { + if (p == player) + continue; + opponentData.add(p.getDetails().getName() + " " + Integer.toString(this.playerChanges.getOrDefault(p, new String[]{}).length)); + } + player.send(new ITEMMSG(new String[]{this.getGameId(), "OPPONENTS", String.join(Character.toString((char) 13), opponentData)})); + } + + private void revealCards() { + // Reveal cards to everyone + List playerUpdates = new ArrayList<>(); + for (Player player : this.playersPlaying) + playerUpdates.add(player.getDetails().getName() + "/" + this.formatRevealCardsUpdate(player)); + + this.sendToPlaying(new ITEMMSG(new String[]{ + this.getGameId(), + "REVEALCARDS", + String.join(Character.toString((char) 13), playerUpdates) + })); + } + + private void endRound() { + this.options.onFinish(playerCards); + this.revealCards(); + this.clearRound(); + } + + private void clearRound() { + this.options.clear(); + this.deck.clear(); + this.playerCards.clear(); + this.playerChanges.clear(); + this.playersPlaying.clear(); + } + + private List getDisconnectedPlayers() { + List toDisconnect = new ArrayList<>(); + + for (Player player: this.playersInGame) { + if (!this.getPlayers().contains(player)) + toDisconnect.add(player); + } + + return toDisconnect; + } + + private boolean isRoundRunning() { + return !this.deck.isEmpty(); + } + + private boolean hasEveryoneChanged() { + return this.playersPlaying.stream().filter(p -> this.playerChanges.containsKey(p)).count() == this.playersPlaying.size(); + } + + /** + * Send message to all players in game (including sender) + * @param messageComposer + */ + public void sendToPlaying(MessageComposer messageComposer) { + for (Player p : this.playersPlaying) { + p.send(messageComposer); + } + } + + /** + * Send message to all opponents in game (excluding sender) + * @param sender sender + * @param messageComposer + */ + public void sentToPlayingOpponents(Player sender, MessageComposer messageComposer) { + for (Player p : this.playersPlaying) { + if (p == sender) + continue; + p.send(messageComposer); + } + } + + @Override public int getMaximumPeopleRequired() { return 4; @@ -41,7 +295,7 @@ public class GamePoker extends GamehallGame { @Override public int getMinimumPeopleRequired() { - return 2; + return 1; } @Override diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameTicTacToe.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameTicTacToe.java index 2b15163..dd1fabe 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameTicTacToe.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GameTicTacToe.java @@ -44,6 +44,7 @@ public class GameTicTacToe extends GamehallGame { this.gameMap = null; } + /* @Override public void joinGame(Player p) { } @@ -51,7 +52,7 @@ public class GameTicTacToe extends GamehallGame { public void leaveGame(Player player) { this.playerSides.remove(player); } - + */ @Override public void handleCommand(Player player, Room room, Item item, String command, String[] args) { GameTrigger trigger = (GameTrigger) item.getDefinition().getInteractionType().getTrigger(); diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamehallGame.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamehallGame.java index 92fea84..f9a9b34 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamehallGame.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/GamehallGame.java @@ -49,17 +49,11 @@ public abstract class GamehallGame { */ public abstract void handleCommand(Player player, Room room, Item item, String command, String[] args); - /** - * Join game handler for player - * @param player the player that join - */ - public abstract void joinGame(Player player); - /** * Leave game handler for player * @param player the player that leaves */ - public abstract void leaveGame(Player player); + // public abstract void leaveGame(Player player); /** * Gets the unique game ID instance for this pair. Will diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerCombination.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerCombination.java new file mode 100644 index 0000000..5a564cb --- /dev/null +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerCombination.java @@ -0,0 +1,107 @@ +package org.alexdev.havana.game.games.gamehalls.utils; + +import java.util.*; +import java.util.stream.Collectors; + +public enum GamePokerCombination { + FIVE_OF_A_KIND("a Five of a kind"), + ROYAL_FLUSH("a Royal flush"), + STRAIGHT_FLUSH("a Straight flush"), + FOUR_OF_A_KIND("a Four of a kind"), + FULL_HOUSE("a Full House"), + FLUSH("a Flush"), + STRAIGHT("a Straight"), + THREE_OF_A_KIND("a Three of a kind"), + TWO_PAIR("two pairs"), + ONE_PAIR("one pair"), + HIGH_CARD("an high card"); + + final static List Faces = Arrays.asList("2","3","4","5","6","7","8","9","10", "11", "12", "13", "1"); + final static List Suits = Arrays.asList("h", "d", "c", "s"); + final static String Joker = "joker"; + + private final String name; + + GamePokerCombination(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public int getCombinationRank() { + return Math.abs(this.ordinal() - 10); + } + + public static List getDeck() { + List deck = new ArrayList<>(Arrays.asList(Joker, Joker)); + + for (String suit : Suits) { + for (String face : Faces) { + deck.add(suit + face); + } + } + + return deck; + } + + public static GamePokerCombination getCombination(List hand) { + List cards = hand.stream() + .filter(card -> !card.equals(Joker)) + .collect(Collectors.toList()); + int jokers = hand.size() - cards.size(); + + List suits = cards.stream() + .map(card -> Suits.indexOf(card.substring(0, 1))) + .collect(Collectors.toList()); + List faces = cards.stream() + .map(card -> Faces.indexOf(card.substring(1))) + .collect(Collectors.toList()); + + + if (cards.stream().anyMatch(card -> Collections.frequency(cards, card) > 1) || + faces.stream().anyMatch(face -> face == -1) || + suits.stream().anyMatch(suit -> suit == -1)) { + return null; + } + + boolean flush = suits.stream().allMatch(suit -> suit.equals(suits.get(0))); + List groups = Faces.stream() + .map(face -> Collections.frequency(faces, Faces.indexOf(face))) + .sorted(Comparator.reverseOrder()) + .collect(Collectors.toList()); + List shifted = faces.stream() + .map(face -> (face + 1) % 13) + .collect(Collectors.toList()); + int distance = Math.min(Collections.max(faces) - Collections.min(faces), + Collections.max(shifted) - Collections.min(shifted)); + boolean straight = groups.get(0) == 1 && distance < 5; + boolean royal = straight && flush && !faces.stream().anyMatch(face -> face < 8); + groups.set(0, groups.get(0) + jokers); + + if (groups.get(0) == 5) { + return GamePokerCombination.FIVE_OF_A_KIND; + } else if (royal) { + return GamePokerCombination.ROYAL_FLUSH; + } else if (straight && flush) { + return GamePokerCombination.STRAIGHT_FLUSH; + } else if (groups.get(0) == 4) { + return GamePokerCombination.FOUR_OF_A_KIND; + } else if (groups.get(0) == 3 && groups.get(1) == 2) { + return GamePokerCombination.FULL_HOUSE; + } else if (flush) { + return GamePokerCombination.FLUSH; + } else if (straight) { + return GamePokerCombination.STRAIGHT; + } else if (groups.get(0) == 3) { + return GamePokerCombination.THREE_OF_A_KIND; + } else if (groups.get(0) == 2 && groups.get(1) == 2) { + return GamePokerCombination.TWO_PAIR; + } else if (groups.get(0) == 2) { + return GamePokerCombination.ONE_PAIR; + } else { + return GamePokerCombination.HIGH_CARD; + } + } +} \ No newline at end of file diff --git a/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerOptions.java b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerOptions.java new file mode 100644 index 0000000..b084241 --- /dev/null +++ b/Havana-Server/src/main/java/org/alexdev/havana/game/games/gamehalls/utils/GamePokerOptions.java @@ -0,0 +1,265 @@ +package org.alexdev.havana.game.games.gamehalls.utils; + +import org.alexdev.havana.dao.mysql.CurrencyDao; +import org.alexdev.havana.game.catalogue.CatalogueItem; +import org.alexdev.havana.game.catalogue.CatalogueManager; +import org.alexdev.havana.game.games.gamehalls.GamePoker; +import org.alexdev.havana.game.player.Player; +import org.alexdev.havana.messages.outgoing.rooms.user.CHAT_MESSAGE; +import org.alexdev.havana.messages.outgoing.user.currencies.CREDIT_BALANCE; +import org.alexdev.havana.messages.outgoing.user.currencies.TICKET_BALANCE; +import org.alexdev.havana.util.DateUtil; +import org.alexdev.havana.util.config.GameConfiguration; +import org.apache.commons.lang3.tuple.Pair; + +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; + +public class GamePokerOptions { + + final private GameConfiguration configuration = GameConfiguration.getInstance(); + final private GamePoker room; + + final private int entryPrice; + final private int minPlayers; + + final private boolean redistributeEntry; + final private boolean redistributeOnTie; + + final private int creditsBonus; + final private boolean creditsBonusOnTie; + + final private String[] raresReward; + final private int raresQuantity; + final private boolean raresOnTie; + + final private int ticketsReward; + final private boolean ticketsOnTie; + + final private boolean announceWinner; + final private boolean announceRewards; + + // Stateful - will reset every round + private int entryPaid = 0; + final private List rewardMessages = new ArrayList<>(); + + + public GamePokerOptions(GamePoker room) { + this.room = room; + this.entryPrice = this.getInteger("poker.entry.price", 0); + this.minPlayers = this.getInteger("poker.reward.min.player", 2); + + this.redistributeEntry = this.getBoolean("poker.entry.price.redistribute", true); + this.redistributeOnTie = this.getBoolean("poker.entry.price.redistribute.on.tie", true); + + this.creditsBonus = this.getInteger("poker.reward.credits.bonus", 0); + this.creditsBonusOnTie = this.getBoolean("poker.reward.credits.bonus.on.tie", false); + + this.raresReward = this.getString("poker.reward.rares", "").split(","); + this.raresQuantity = this.getInteger("poker.reward.rares.quantity", 0); + this.raresOnTie = this.getBoolean("poker.reward.rares.on.tie", false); + + this.ticketsReward = this.getInteger("poker.reward.tickets", 0); + this.ticketsOnTie = this.getBoolean("poker.reward.tickets.on.tie", false); + + this.announceWinner = this.getBoolean("poker.announce.winner", true); + this.announceRewards = this.getBoolean("poker.announce.rewards", false); + } + + public void onEntry(Player player) { + if (this.entryPrice <= 0) + return; + CurrencyDao.decreaseCredits(player.getDetails(), this.entryPrice); + player.send(new CREDIT_BALANCE(player.getDetails().getCredits())); + this.entryPaid += this.entryPrice; + } + + public void clear() { + this.entryPaid = 0; + this.rewardMessages.clear(); + } + + public void onFinish(Map> _players) { + List playing = _players.keySet().stream().collect(Collectors.toList()); + Pair> winners = this.getWinners(_players); + GamePokerCombination winningCombination = winners.getLeft(); + List winnerList = winners.getRight(); + + if (this.announceWinner) + this.showChat(playing, this.getWinnerMessage(winningCombination, winnerList)); + + if (playing.size() < this.minPlayers || !this.canReward(winnerList)) + return; + + this.rewardCredits(winnerList); + this.rewardRares(winnerList); + this.rewardTickets(winnerList); + + if (this.announceRewards) + this.rewardMessages.forEach(msg -> this.showChat(playing, msg)); + } + + private void rewardCredits(List winners) { + int credits = 0; + + if (this.redistributeEntry && (winners.size() == 1 || this.redistributeOnTie)) + credits += this.entryPaid; + + if (this.creditsBonus > 0 && (winners.size() == 1 || this.creditsBonusOnTie)) + credits += this.creditsBonus; + + if (credits == 0) + return; + + int creditsPerWinner = (int) Math.floor(credits / winners.size()); + + for (Player winner : winners) { + CurrencyDao.increaseCredits(winner.getDetails(), creditsPerWinner); + winner.send(new CREDIT_BALANCE(winner.getDetails().getCredits())); + } + + if (winners.size() > 1) + this.rewardMessages.add("Each winner received " + creditsPerWinner + " credits!"); + else + this.rewardMessages.add(winners.get(0).getDetails().getName() + " received " + creditsPerWinner + " credits!"); + } + + private void rewardRares(List winners) { + if (this.raresReward.length == 0 || this.raresQuantity == 0 || (winners.size() > 1 && !this.raresOnTie)) + return; + + CatalogueManager catalogue = CatalogueManager.getInstance(); + CatalogueItem[] rares = new CatalogueItem[this.raresQuantity]; + String[] rareNames = new String[this.raresQuantity]; + + for (int i = 0; i < this.raresQuantity; i++) { + int randomIndex = ThreadLocalRandom.current().nextInt(0, this.raresReward.length); + CatalogueItem rare = catalogue.getCatalogueItem(this.raresReward[randomIndex]); + rares[i] = rare; + rareNames[i] = rare.getDefinition().getName(); + } + + + for (Player winner : winners) { + for (CatalogueItem rare : rares) { + try { + catalogue.purchase(winner.getDetails(), rare, null, null, DateUtil.getCurrentTimeSeconds()); + } catch (SQLException e) { + // todo: handle exception + e.printStackTrace(); + return; + } + } + winner.getInventory().getView("new"); + // winner.send(new ITEM_DELIVERED()); + } + + if (winners.size() > 1) + this.rewardMessages.add("Each winner received " + String.join(", ", rareNames) + " !"); + else + this.rewardMessages.add(winners.get(0).getDetails().getName() + " received " + String.join(", ", rareNames) + " !"); + } + + private void rewardTickets(List winners) { + if (this.ticketsReward <= 0 || (winners.size() > 1 && !this.ticketsOnTie)) + return; + + for (Player winner : winners) { + CurrencyDao.increaseTickets(winner.getDetails(), this.ticketsReward); + winner.send(new TICKET_BALANCE(winner.getDetails().getTickets())); + } + + if (winners.size() > 1) + this.rewardMessages.add("Each winner received " + this.ticketsReward + " tickets!"); + else + this.rewardMessages.add(winners.get(0).getDetails().getName() + " received " + this.ticketsReward + " tickets!"); + } + /** + * Show chat message to all players in game + * @param chat + */ + private void showChat(List players, String chat) { + for (Player p : players) { + p.send(new CHAT_MESSAGE(CHAT_MESSAGE.ChatMessageType.CHAT, p.getRoomUser().getInstanceId(), chat, 0)); + } + } + + /** + * Does the option apply to the current room? + * @param option + * @return + */ + private boolean activeInRoom(String option) { + String rooms = this.configuration.getString(option + ".only.in.rooms", ""); + return rooms.isBlank() || rooms.contains(Integer.toString(this.room.getRoomId())); + } + + private String getString(String name, String def) { + String value = this.configuration.getString(name, def); + if (!this.activeInRoom(name)) + return def; + return value; + } + + private int getInteger(String name, int def) { + int value = this.configuration.getInteger(name); + if (!this.activeInRoom(name)) + return def; + return value; + } + + private boolean getBoolean(String name, boolean def) { + boolean value = this.configuration.getBoolean(name); + if (!this.activeInRoom(name)) + return def; + return value; + } + + private boolean canReward(List winners) { + boolean isTie = winners.size() > 1; + return ( + (this.redistributeEntry && this.entryPaid > 0 && (!isTie || this.redistributeOnTie)) || + (this.raresReward.length > 0 && (!isTie || this.raresOnTie)) || + (this.ticketsReward > 0 && (!isTie || this.ticketsOnTie)) || + (this.creditsBonus > 0 && (!isTie || this.creditsBonusOnTie)) + ); + } + + private String getWinnerMessage(GamePokerCombination combination, List winners) { + if (winners.size() == 1) { + String name = winners.get(0).getDetails().getName(); + return name + " wins this round with " + combination.getName() + ". Congratulations!"; + } else { + String names = String.join(", ", winners.stream().map(p -> p.getDetails().getName()).collect(Collectors.toList())); + return "It's a tie! " + names + " all have " + combination.getName() + ". Congratulations!"; + } + } + + private Pair> getWinners(Map> players) { + List winners = new ArrayList<>(); + GamePokerCombination winningCombination = null; + + // Calculate winners + for (Player player : players.keySet()) { + List hand = players.get(player); + GamePokerCombination combination = GamePokerCombination.getCombination(hand); + if (winningCombination == null) { + winningCombination = combination; + winners.add(player); + continue; + } + if (combination.getCombinationRank() > winningCombination.getCombinationRank()) { + winningCombination = combination; + winners.clear(); + winners.add(player); + } else if (combination.getCombinationRank() == winningCombination.getCombinationRank()) { + winners.add(player); + } + } + return Pair.of(winningCombination, winners); + } +} diff --git a/Havana-Server/src/main/java/org/alexdev/havana/messages/outgoing/rooms/games/ITEMMSG.java b/Havana-Server/src/main/java/org/alexdev/havana/messages/outgoing/rooms/games/ITEMMSG.java index 76ab3e7..3fcdf0e 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/messages/outgoing/rooms/games/ITEMMSG.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/messages/outgoing/rooms/games/ITEMMSG.java @@ -5,15 +5,40 @@ import org.alexdev.havana.server.netty.streams.NettyResponse; public class ITEMMSG extends MessageComposer { private final String[] commands; + private final boolean delimitate; + /** + * Create a new ITEMMSG with multiple commands. + * Each line will be delimited with '\r' + * @param commands + */ public ITEMMSG(String[] commands) { this.commands = commands; + this.delimitate = true; + } + + /** + * Create a new ITEMMSG with a single command. + * This expects the command to be already delimited + * @param command + */ + public ITEMMSG(String command) { + this.commands = new String[]{command}; + this.delimitate = false; } @Override public void compose(NettyResponse response) { - response.write(String.join(Character.toString((char)13), this.commands)); + for (String value : this.commands) { + if (this.delimitate) { + response.write(value, '\r'); + response.write('\r'); + } + else { + response.write(value); + } } + } @Override public short getHeader() { diff --git a/Havana-Server/src/main/java/org/alexdev/havana/util/config/writer/GameConfigWriter.java b/Havana-Server/src/main/java/org/alexdev/havana/util/config/writer/GameConfigWriter.java index 8a0046a..a52fb8c 100644 --- a/Havana-Server/src/main/java/org/alexdev/havana/util/config/writer/GameConfigWriter.java +++ b/Havana-Server/src/main/java/org/alexdev/havana/util/config/writer/GameConfigWriter.java @@ -139,6 +139,34 @@ public class GameConfigWriter implements ConfigWriter { config.put("messenger.enable.official.update.speed", "false"); + config.put("poker.entry.price", "0"); + config.put("poker.entry.price.only.in.rooms", ""); + + config.put("poker.entry.price.redistribute", "true"); + config.put("poker.entry.price.redistribute.on.tie", "true"); + config.put("poker.entry.price.redistribute.only.in.rooms", ""); + + config.put("poker.reward.min.player", "2"); + config.put("poker.reward.min.player.only.in.rooms", ""); + + config.put("poker.reward.credits.bonus", "0"); + config.put("poker.reward.credits.bonus.on.tie", "false"); + config.put("poker.reward.credits.bonus.only.in.rooms", ""); + + config.put("poker.reward.rares", ""); + config.put("poker.reward.rares.only.in.rooms", ""); + config.put("poker.reward.rares.quantity", "1"); + config.put("poker.reward.rares.on.tie", "false"); + + config.put("poker.reward.tickets", "0"); + config.put("poker.reward.tickets.on.tie", "false"); + config.put("poker.reward.tickets.only.in.rooms", ""); + + config.put("poker.announce.winner", "false"); + config.put("poker.announce.winner.only.in.rooms", "0"); + config.put("poker.announce.rewards", "false"); + config.put("poker.announce.rewards.only.in.rooms", ""); + for (var set : CommandManager.getCommands()) { if (set.getValue().getPlayerRank().getRankId() > 1) { config.put("groups.ids.permission." + set.getKey()[0], "");