From 0146804287b84537966e57b8eb6635454d1064b3 Mon Sep 17 00:00:00 2001 From: nhaar <38634785+nhaar@users.noreply.github.com> Date: Mon, 16 Sep 2024 14:36:42 -0300 Subject: [PATCH] add card-jitsu end game screen --- houdini/handlers/games/__init__.py | 19 ++++++- houdini/handlers/games/ninja/card.py | 73 ++++++++++++++------------- houdini/handlers/games/ninja/fire.py | 32 ++++++++---- houdini/handlers/games/ninja/water.py | 45 +++++++++++++---- houdini/penguin.py | 13 +++++ 5 files changed, 124 insertions(+), 58 deletions(-) diff --git a/houdini/handlers/games/__init__.py b/houdini/handlers/games/__init__.py index 3d6acc7..8d281dc 100644 --- a/houdini/handlers/games/__init__.py +++ b/houdini/handlers/games/__init__.py @@ -78,8 +78,21 @@ async def handle_get_game_over(p, score: int): if p.room.id == 996: return - if p.room.game and not p.waddle and not p.table: + # card-jitsus except snow have special handling + card_jitsu_rooms = [995, 998, 997] + is_card_jitsu = p.room.id in card_jitsu_rooms + + # Waddle minigames don't normally need the end screen + if p.waddle and not is_card_jitsu: + return + + if p.room.game and not p.table: coins_earned = determine_coins_earned(p, score) + + if not is_card_jitsu: + if await determine_coins_overdose(p, coins_earned): + return await cheat_ban(p, p.id, comment="Coins overdose") + stamp_info = "", 0, 0, 0 if p.room.stamp_group: @@ -88,6 +101,10 @@ async def handle_get_game_over(p, score: int): if stamp_info[1] == stamp_info[2]: coins_earned *= 2 + if not is_card_jitsu: + await p.update( + coins=min(p.coins + coins_earned, p.server.config.max_coins) + ).apply() await p.send_xt("zo", p.coins, *stamp_info) diff --git a/houdini/handlers/games/ninja/card.py b/houdini/handlers/games/ninja/card.py index 42f1d09..d307643 100644 --- a/houdini/handlers/games/ninja/card.py +++ b/houdini/handlers/games/ninja/card.py @@ -1,6 +1,7 @@ import itertools import math import random +import enum from collections import Counter from dataclasses import dataclass from typing import Dict, List, Union @@ -10,6 +11,19 @@ from houdini.data.ninja import Card from houdini.handlers import XTPacket from houdini.penguin import Penguin +class CardStamp(enum.IntEnum): + """ID of Card-Jitsu stamps""" + GRASSHOPPER = 230 + ELEMENTAL_WIN = 242 + FINE_STUDENT = 232 + FLAWLESS_VICTORY = 238 + ONE_ELEMENT = 244 + TRUE_NINJA = 234 + MATCH_MASTER = 240 + NINJA_MASTER = 236 + SENSEI_CARD = 246 + FULL_DOJO = 248 + @dataclass class Played: @@ -44,7 +58,12 @@ class CardJitsuLogic(IWaddle): ItemAwards = [4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 104] PostcardAwards = {0: 177, 4: 178, 8: 179} - StampAwards = {0: 230, 4: 232, 8: 234, 9: 236} + StampAwards = { + 0: CardStamp.GRASSHOPPER, + 4: CardStamp.FINE_STUDENT, + 8: CardStamp.TRUE_NINJA, + 9: CardStamp.NINJA_MASTER, + } StampGroupId = 38 def __init__(self, waddle): @@ -235,7 +254,7 @@ async def ninja_rank_up(p, ranks=1): if rank in CardJitsuLogic.PostcardAwards: await p.add_inbox(p.server.postcards[CardJitsuLogic.PostcardAwards[rank]]) if rank in CardJitsuLogic.StampAwards: - await p.add_stamp(p.server.stamps[CardJitsuLogic.StampAwards[rank]]) + await p.add_card_jitsu_stamp(CardJitsuLogic.StampAwards[rank]) await p.update(ninja_rank=p.ninja_rank + ranks).apply() return True @@ -266,20 +285,10 @@ async def ninja_progress(p, won=False): await ninja_rank_up(p) await p.send_xt('cza', p.ninja_rank) -async def ninja_stamps_earned(p): - game_stamps = [stamp for stamp in p.server.stamps.values() if stamp.group_id == p.room.stamp_group] - collected_stamps = [stamp for stamp in game_stamps if stamp.id in p.stamps] - total_collected_stamps = len(collected_stamps) - total_game_stamps = len(game_stamps) - collected_stamps_string = '|'.join(str(stamp.id) for stamp in collected_stamps) - await p.send_xt('cjsi', collected_stamps_string, total_collected_stamps, total_game_stamps) - async def ninja_win(winner, loser): await ninja_progress(winner.penguin, won=True) await ninja_progress(loser.penguin, won=False) - await ninja_stamps_earned(winner.penguin) - await ninja_stamps_earned(loser.penguin) await winner.penguin.waddle.remove_penguin(winner.penguin) await loser.penguin.waddle.remove_penguin(loser.penguin) @@ -304,6 +313,8 @@ async def handle_update_game(p): @handlers.handler(XTPacket('lz', ext='z')) @handlers.waddle(CardJitsuLogic, CardJitsuMatLogic, SenseiLogic) async def handle_leave_game(p): + # sending stamp info fixes the game taking a bit to close when quitting + await p.send_card_jitsu_stamp_info() seat_id = p.waddle.get_seat_id(p) await p.waddle.send_xt('cz', p.safe_name, f=lambda penguin: penguin is not p) await p.waddle.send_xt('lz', seat_id, f=lambda penguin: penguin is not p) @@ -357,9 +368,8 @@ async def handle_send_pick(p, action: str, card_id: int): winner_seat_id = p.waddle.get_round_winner() if me.chosen.card.id == 256 or opponent.chosen.card.id == 256: - stamp = p.server.stamps[246] - await me.penguin.add_stamp(stamp, notify=True) - await opponent.penguin.add_stamp(stamp, notify=True) + await me.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD) + await opponent.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD) if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: await p.waddle.send_xt('zm', 'power', seat_id, opponent_seat_id, me.chosen.card.power_id) @@ -383,19 +393,18 @@ async def handle_send_pick(p, action: str, card_id: int): if winning_cards: await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in winning_cards)) - stamp = p.server.stamps[[244, 242][win_method]] - await winner.penguin.add_stamp(stamp, notify=True) + stamp = [CardStamp.ONE_ELEMENT, CardStamp.ELEMENTAL_WIN][win_method] + await winner.penguin.add_card_jitsu_stamp(stamp) if all(not cards for cards in loser.bank.values()): - stamp = p.server.stamps[238] - await winner.penguin.add_stamp(stamp, notify=True) + await winner.penguin.add_card_jitsu_stamp( + CardStamp.FLAWLESS_VICTORY + ) if sum(1 for cards in winner.bank.values() for _ in cards) >= 9: - stamp = p.server.stamps[248] - await winner.penguin.add_stamp(stamp, notify=True) + await winner.penguin.add_card_jitsu_stamp(CardStamp.FULL_DOJO) await winner.penguin.update(ninja_matches_won=winner.penguin.ninja_matches_won+1).apply() if winner.penguin.ninja_matches_won == 25: - stamp = p.server.stamps[240] - await winner.penguin.add_stamp(stamp, notify=True) + await winner.penguin.add_card_jitsu_stamp(CardStamp.MATCH_MASTER) await ninja_win(winner, loser) else: @@ -493,8 +502,7 @@ async def handle_send_sensei_pick(p, action: str, card_id: int): winner_seat_id = p.waddle.get_round_winner() if me.chosen.card.id == 256 or sensei.chosen.card.id == 256: - stamp = p.server.stamps[246] - await p.add_stamp(stamp, notify=True) + await me.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD) if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: await p.send_xt('zm', 'power', 1, 0, me.chosen.card.power_id) @@ -518,21 +526,17 @@ async def handle_send_sensei_pick(p, action: str, card_id: int): await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in winning_cards)) if winner == me: - stamp = p.server.stamps[[244, 242][win_method]] - await p.add_stamp(stamp, notify=True) + stamp = [CardStamp.ONE_ELEMENT, CardStamp.ELEMENTAL_WIN][win_method] + await me.penguin.add_card_jitsu_stamp(stamp) if all(not cards for cards in sensei.bank.values()): - stamp = p.server.stamps[238] - await p.add_stamp(stamp, notify=True) + await me.penguin.add_card_jitsu_stamp(CardStamp.FLAWLESS_VICTORY) if sum(1 for cards in me.bank.values() for _ in cards) >= 9: - stamp = p.server.stamps[248] - await p.add_stamp(stamp, notify=True) + await me.penguin.add_card_jitsu_stamp(CardStamp.FULL_DOJO) await p.update(ninja_matches_won=p.ninja_matches_won + 1).apply() if p.ninja_matches_won == 25: - stamp = p.server.stamps[240] - await p.add_stamp(stamp, notify=True) + await me.penguin.add_card_jitsu_stamp(CardStamp.MATCH_MASTER) - await ninja_stamps_earned(p) can_rank_up = await ninja_rank_up(p) if can_rank_up: await p.send_xt('cza', p.ninja_rank) @@ -548,7 +552,6 @@ async def handle_send_sensei_pick(p, action: str, card_id: int): if can_rank_up: await p.send_xt('cza', p.ninja_rank) await p.waddle.send_xt('czo', 0, winner_seat_id) - await ninja_stamps_earned(p) await p.send_xt('zm', 'judge', winner_seat_id) me.chosen = None diff --git a/houdini/handlers/games/ninja/fire.py b/houdini/handlers/games/ninja/fire.py index a4041e4..11b1dd0 100644 --- a/houdini/handlers/games/ninja/fire.py +++ b/houdini/handlers/games/ninja/fire.py @@ -2,6 +2,7 @@ import asyncio import itertools import math import random +import enum from collections import Counter from dataclasses import dataclass, field from typing import List, Union @@ -9,9 +10,18 @@ from typing import List, Union from houdini import IWaddle, handlers from houdini.data.ninja import Card from houdini.handlers import XTPacket -from houdini.handlers.games.ninja.card import ninja_stamps_earned from houdini.penguin import Penguin +class FireStamp(enum.IntEnum): + """IDs of Card-Jitsu Fire stamps""" + WARM_UP = 252 + SCORE_FIRE = 254 + FIRE_MIDWAY = 256 + STRONG_DEFENCE = 260 + FIRE_SUIT = 262 + FIRE_NINJA = 264 + MAX_ENERGY = 266 + FIRE_EXPERT = 268 @dataclass class FireNinja: @@ -37,7 +47,7 @@ class CardJitsuFireLogic(IWaddle): AutoBattleTimeout = 22 ItemAwards = [6025, 4120, 2013, 1086, 3032] - StampAwards = {2: 256, 4: 262} + StampAwards = {2: FireStamp.FIRE_MIDWAY, 4: FireStamp.FIRE_SUIT} def __init__(self, waddle): super().__init__(waddle) @@ -524,7 +534,7 @@ async def fire_ninja_rank_up(p, ranks=1): for rank in range(p.fire_ninja_rank, p.fire_ninja_rank+ranks): await p.add_inventory(p.server.items[CardJitsuFireLogic.ItemAwards[rank]], notify=False) if rank in CardJitsuFireLogic.StampAwards: - await p.add_stamp(p.server.stamps[CardJitsuFireLogic.StampAwards[rank]]) + await p.add_card_jitsu_stamp(CardJitsuFireLogic.StampAwards[rank]) await p.update( fire_ninja_rank=p.fire_ninja_rank + ranks ).apply() @@ -567,18 +577,18 @@ async def end_game_stamps(ninja, finish_position): if finish_position == 1: await ninja.penguin.update(fire_matches_won=ninja.penguin.fire_matches_won + 1).apply() if ninja.penguin.fire_matches_won >= 10: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[252]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.WARM_UP) if ninja.penguin.fire_matches_won >= 50: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[268]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.FIRE_EXPERT) if ninja.energy >= 6: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[260]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.STRONG_DEFENCE) if type(ninja.penguin.waddle) == FireSenseiLogic: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[264]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.FIRE_NINJA) if ninja.energy_won >= 1: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[254]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.SCORE_FIRE) if ninja.energy_won >= 3: - await ninja.penguin.add_stamp(ninja.penguin.server.stamps[266]) + await ninja.penguin.add_card_jitsu_stamp(FireStamp.MAX_ENERGY) @handlers.handler(XTPacket('gz', ext='z')) @@ -665,5 +675,5 @@ async def handle_info_ready_sync(p): @handlers.handler(XTPacket('lz', ext='z')) @handlers.player_in_room(CardJitsuFireLogic.room_id) -async def handle_leave_game(p): - await ninja_stamps_earned(p) +async def handle_leave_game(p: Penguin): + await p.send_card_jitsu_stamp_info() diff --git a/houdini/handlers/games/ninja/water.py b/houdini/handlers/games/ninja/water.py index 9988b18..4562216 100644 --- a/houdini/handlers/games/ninja/water.py +++ b/houdini/handlers/games/ninja/water.py @@ -12,6 +12,17 @@ from houdini.handlers import XTPacket from houdini.penguin import Penguin from houdini.data.ninja import Card +class WaterStamp(enum.IntEnum): + "IDs of Card-Jitsu Water stamps" + GONG = 270 + WATERY_FALL = 274 + WATER_EXPERT = 276 + WATER_MIDWAY = 278 + WATER_SUIT = 282 + WATER_NINJA = 284 + TWO_CLOSE = 286 + SKIPPING_STONES = 288 + @dataclass class WaterCard: @@ -252,6 +263,13 @@ class WaterPlayer: cleared: int = 0 """Number of stones cleared in the match""" + left: bool = False + """ + Whether the player has left the game + + Penguins remain in the board even if the player leaves + """ + def get_card(self, hand_id: int) -> WaterCard: """Get the card given its hand ID""" return next((card for card in self.hand.cards if card.hand_id == hand_id), None) @@ -407,7 +425,11 @@ class CardJitsuWaterLogic(IWaddle): ITEM_AWARDS = [6026, 4121, 2025, 1087, 3032] """All the items gained from ranking, indexed by their rank""" - STAMP_AWARDS = {1: 278, 3: 282, 4: 284} + STAMP_AWARDS = { + 1: WaterStamp.WATER_MIDWAY, + 3: WaterStamp.WATER_SUIT, + 4: WaterStamp.WATER_NINJA, + } """Map rank and the stamp you gain from LEAVING the rank""" board_cycle_handler: WaterCycleHandler @@ -643,15 +665,12 @@ class CardJitsuWaterLogic(IWaddle): ).apply() if winner.penguin.water_matches_won >= 100: - # Water Expert stamp - await winner.penguin.add_stamp(winner.penguin.server.stamps[276]) + await winner.penguin.add_card_jitsu_stamp(WaterStamp.WATER_EXPERT) - # Gong! stamp - await winner.penguin.add_stamp(winner.penguin.server.stamps[270]) + await winner.penguin.add_card_jitsu_stamp(WaterStamp.GONG) if winner.two_close >= 2: - # Two Close stamp - await winner.penguin.add_stamp(winner.penguin.server.stamps[286]) + await winner.penguin.add_card_jitsu_stamp(WaterStamp.TWO_CLOSE) # iterate over all players that drowned from the last place order for row in self.board.rows: @@ -693,7 +712,7 @@ class CardJitsuWaterLogic(IWaddle): p.server.items[cls.ITEM_AWARDS[rank]], cost=0, notify=False ) if rank in cls.STAMP_AWARDS: - await p.add_stamp(p.server.stamps[cls.STAMP_AWARDS[rank]]) + await p.add_card_jitsu_stamp(cls.STAMP_AWARDS[rank]) await p.update(water_ninja_rank=p.water_ninja_rank + ranks).apply() return True @@ -714,6 +733,8 @@ class CardJitsuWaterLogic(IWaddle): await player.penguin.add_stamp(player.penguin.server.stamps[274]) + await player.penguin.add_card_jitsu_stamp(WaterStamp.WATERY_FALL) + # CMD_PLAYER_KILL, meant for players who lose from falling player_kill_data = [] for player in players_in_row: @@ -997,12 +1018,15 @@ def get_water_rank_threshold(rank): async def handle_get_game(p: Penguin): """Handle the client entering the game""" seat_id = p.waddle.get_seat_id(p) - player = p.waddle.get_player_by_penguin(p) + player: WaterPlayer = p.waddle.get_player_by_penguin(p) # needs to send these or the client dies await p.send_xt("gz") await p.send_xt("jz") + # needed to fix client taking a bit to exit game + await p.send_card_jitsu_stamp_info() + # CMD_PLAYER_INDEX await p.waddle.send_zm_client(player, "po", seat_id) @@ -1121,8 +1145,7 @@ async def handle_throw_card(p: Penguin, *, cell_id: str): ) if player.cleared >= 28: - # Skipping Stones stamp - await player.penguin.add_stamp(player.penguin.server.stamps[288]) + await p.add_card_jitsu_stamp(WaterStamp.SKIPPING_STONES) # CMD_PLAYER_THROW await p.waddle.send_zm( diff --git a/houdini/penguin.py b/houdini/penguin.py index 45392c5..bd405c0 100644 --- a/houdini/penguin.py +++ b/houdini/penguin.py @@ -380,6 +380,19 @@ class Penguin(Spheniscidae, penguin.Penguin): self.logger.info(f'{self.username} updated their background to \'{item.name}\' ' if item else f'{self.username} removed their background item') + + async def send_card_jitsu_stamp_info(self): + """ + Send information the client requires to properly display the stamp end screen + """ + stamp_info = await self.get_game_end_stamps_info(False) + await self.send_xt("cjsi", *stamp_info) + + async def add_card_jitsu_stamp(self, stamp_id): + """Correct way of adding a card-jitsu (Regular, Fire, Water) stamp""" + await self.add_stamp(self.server.stamps[stamp_id]) + await self.send_card_jitsu_stamp_info() + async def get_game_end_stamps_info( self, clear_session: bool ) -> tuple[str, int, int, int]: