Improve stamps end screen (#101)

* properly handle players exiting cardjitsu water

* make end screens only show stamps from current session

* add card-jitsu end game screen

* fix oversight with how water players get removed

* fix card jitsu fire stamp awards

* change session stamps to use redis
This commit is contained in:
nhaar 2024-10-12 01:16:52 -03:00 committed by GitHub
parent f6f9f39c22
commit e23a7ecf2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 234 additions and 83 deletions

View File

@ -78,30 +78,34 @@ async def handle_get_game_over(p, score: int):
if p.room.id == 996: if p.room.id == 996:
return 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) coins_earned = determine_coins_earned(p, score)
if await determine_coins_overdose(p, coins_earned):
return await cheat_ban(p, p.id, comment='Coins overdose')
collected_stamps_string, total_collected_stamps, total_game_stamps, total_stamps = '', 0, 0, 0 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: if p.room.stamp_group:
game_stamps = [stamp for stamp in p.server.stamps.values() if stamp.group_id == p.room.stamp_group] stamp_info = await p.get_game_end_stamps_info(True)
collected_stamps = [stamp for stamp in game_stamps if stamp.id in p.stamps] # has all stamps in game
if stamp_info[1] == stamp_info[2]:
total_stamps = len([stamp for stamp in p.stamps.values() if p.server.stamps[stamp.stamp_id].group_id])
total_collected_stamps = len(collected_stamps)
total_game_stamps = len(game_stamps)
collected_stamps_string = '|'.join(str(stamp.id) for stamp in collected_stamps)
if total_collected_stamps == total_game_stamps:
coins_earned *= 2 coins_earned *= 2
await p.update(coins=min(p.coins + coins_earned, p.server.config.max_coins)).apply() if not is_card_jitsu:
await p.send_xt('zo', p.coins, await p.update(
collected_stamps_string, coins=min(p.coins + coins_earned, p.server.config.max_coins)
total_collected_stamps, ).apply()
total_game_stamps, await p.send_xt("zo", p.coins, *stamp_info)
total_stamps)
@handlers.handler(XTPacket('ggd', ext='z'), client=ClientType.Vanilla) @handlers.handler(XTPacket('ggd', ext='z'), client=ClientType.Vanilla)
@ -146,3 +150,10 @@ async def handle_game_complete(p, medals: int):
medals = min(6, medals) medals = min(6, medals)
await p.update(career_medals=p.career_medals + medals, await p.update(career_medals=p.career_medals + medals,
agent_medals=p.agent_medals + medals).apply() agent_medals=p.agent_medals + medals).apply()
@handlers.disconnected
@handlers.player_attribute(joined_world=True)
async def clear_stamp_sessions(p):
"""When disconnected, clear stamps in case any were obtained and not properly handled"""
await p.clear_stamps_session()

View File

@ -1,6 +1,7 @@
import itertools import itertools
import math import math
import random import random
import enum
from collections import Counter from collections import Counter
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, List, Union from typing import Dict, List, Union
@ -10,6 +11,19 @@ from houdini.data.ninja import Card
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.penguin import Penguin 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 @dataclass
class Played: class Played:
@ -44,7 +58,12 @@ class CardJitsuLogic(IWaddle):
ItemAwards = [4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 104] ItemAwards = [4025, 4026, 4027, 4028, 4029, 4030, 4031, 4032, 4033, 104]
PostcardAwards = {0: 177, 4: 178, 8: 179} 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 StampGroupId = 38
def __init__(self, waddle): def __init__(self, waddle):
@ -235,7 +254,7 @@ async def ninja_rank_up(p, ranks=1):
if rank in CardJitsuLogic.PostcardAwards: if rank in CardJitsuLogic.PostcardAwards:
await p.add_inbox(p.server.postcards[CardJitsuLogic.PostcardAwards[rank]]) await p.add_inbox(p.server.postcards[CardJitsuLogic.PostcardAwards[rank]])
if rank in CardJitsuLogic.StampAwards: 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() await p.update(ninja_rank=p.ninja_rank + ranks).apply()
return True return True
@ -266,20 +285,10 @@ async def ninja_progress(p, won=False):
await ninja_rank_up(p) await ninja_rank_up(p)
await p.send_xt('cza', p.ninja_rank) 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): async def ninja_win(winner, loser):
await ninja_progress(winner.penguin, won=True) await ninja_progress(winner.penguin, won=True)
await ninja_progress(loser.penguin, won=False) 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 winner.penguin.waddle.remove_penguin(winner.penguin)
await loser.penguin.waddle.remove_penguin(loser.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.handler(XTPacket('lz', ext='z'))
@handlers.waddle(CardJitsuLogic, CardJitsuMatLogic, SenseiLogic) @handlers.waddle(CardJitsuLogic, CardJitsuMatLogic, SenseiLogic)
async def handle_leave_game(p): 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) 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('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) 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() winner_seat_id = p.waddle.get_round_winner()
if me.chosen.card.id == 256 or opponent.chosen.card.id == 256: if me.chosen.card.id == 256 or opponent.chosen.card.id == 256:
stamp = p.server.stamps[246] await me.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD)
await me.penguin.add_stamp(stamp, notify=True) await opponent.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD)
await opponent.penguin.add_stamp(stamp, notify=True)
if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: 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) 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: if winning_cards:
await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in 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]] stamp = [CardStamp.ONE_ELEMENT, CardStamp.ELEMENTAL_WIN][win_method]
await winner.penguin.add_stamp(stamp, notify=True) await winner.penguin.add_card_jitsu_stamp(stamp)
if all(not cards for cards in loser.bank.values()): if all(not cards for cards in loser.bank.values()):
stamp = p.server.stamps[238] await winner.penguin.add_card_jitsu_stamp(
await winner.penguin.add_stamp(stamp, notify=True) CardStamp.FLAWLESS_VICTORY
)
if sum(1 for cards in winner.bank.values() for _ in cards) >= 9: if sum(1 for cards in winner.bank.values() for _ in cards) >= 9:
stamp = p.server.stamps[248] await winner.penguin.add_card_jitsu_stamp(CardStamp.FULL_DOJO)
await winner.penguin.add_stamp(stamp, notify=True)
await winner.penguin.update(ninja_matches_won=winner.penguin.ninja_matches_won+1).apply() await winner.penguin.update(ninja_matches_won=winner.penguin.ninja_matches_won+1).apply()
if winner.penguin.ninja_matches_won == 25: if winner.penguin.ninja_matches_won == 25:
stamp = p.server.stamps[240] await winner.penguin.add_card_jitsu_stamp(CardStamp.MATCH_MASTER)
await winner.penguin.add_stamp(stamp, notify=True)
await ninja_win(winner, loser) await ninja_win(winner, loser)
else: 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() winner_seat_id = p.waddle.get_round_winner()
if me.chosen.card.id == 256 or sensei.chosen.card.id == 256: if me.chosen.card.id == 256 or sensei.chosen.card.id == 256:
stamp = p.server.stamps[246] await me.penguin.add_card_jitsu_stamp(CardStamp.SENSEI_CARD)
await p.add_stamp(stamp, notify=True)
if me.chosen.card.power_id and me.chosen.card.power_id in CardJitsuLogic.OnPlayed: 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) 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)) await p.waddle.send_xt('czo', 0, winner_seat_id, *(card.id for card in winning_cards))
if winner == me: if winner == me:
stamp = p.server.stamps[[244, 242][win_method]] stamp = [CardStamp.ONE_ELEMENT, CardStamp.ELEMENTAL_WIN][win_method]
await p.add_stamp(stamp, notify=True) await me.penguin.add_card_jitsu_stamp(stamp)
if all(not cards for cards in sensei.bank.values()): if all(not cards for cards in sensei.bank.values()):
stamp = p.server.stamps[238] await me.penguin.add_card_jitsu_stamp(CardStamp.FLAWLESS_VICTORY)
await p.add_stamp(stamp, notify=True)
if sum(1 for cards in me.bank.values() for _ in cards) >= 9: if sum(1 for cards in me.bank.values() for _ in cards) >= 9:
stamp = p.server.stamps[248] await me.penguin.add_card_jitsu_stamp(CardStamp.FULL_DOJO)
await p.add_stamp(stamp, notify=True)
await p.update(ninja_matches_won=p.ninja_matches_won + 1).apply() await p.update(ninja_matches_won=p.ninja_matches_won + 1).apply()
if p.ninja_matches_won == 25: if p.ninja_matches_won == 25:
stamp = p.server.stamps[240] await me.penguin.add_card_jitsu_stamp(CardStamp.MATCH_MASTER)
await p.add_stamp(stamp, notify=True)
await ninja_stamps_earned(p)
can_rank_up = await ninja_rank_up(p) can_rank_up = await ninja_rank_up(p)
if can_rank_up: if can_rank_up:
await p.send_xt('cza', p.ninja_rank) 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: if can_rank_up:
await p.send_xt('cza', p.ninja_rank) await p.send_xt('cza', p.ninja_rank)
await p.waddle.send_xt('czo', 0, winner_seat_id) await p.waddle.send_xt('czo', 0, winner_seat_id)
await ninja_stamps_earned(p)
await p.send_xt('zm', 'judge', winner_seat_id) await p.send_xt('zm', 'judge', winner_seat_id)
me.chosen = None me.chosen = None

View File

@ -2,6 +2,7 @@ import asyncio
import itertools import itertools
import math import math
import random import random
import enum
from collections import Counter from collections import Counter
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List, Union from typing import List, Union
@ -9,9 +10,18 @@ from typing import List, Union
from houdini import IWaddle, handlers from houdini import IWaddle, handlers
from houdini.data.ninja import Card from houdini.data.ninja import Card
from houdini.handlers import XTPacket from houdini.handlers import XTPacket
from houdini.handlers.games.ninja.card import ninja_stamps_earned
from houdini.penguin import Penguin 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 @dataclass
class FireNinja: class FireNinja:
@ -37,7 +47,7 @@ class CardJitsuFireLogic(IWaddle):
AutoBattleTimeout = 22 AutoBattleTimeout = 22
ItemAwards = [6025, 4120, 2013, 1086, 3032] ItemAwards = [6025, 4120, 2013, 1086, 3032]
StampAwards = {2: 256, 4: 262} StampAwards = {1: FireStamp.FIRE_MIDWAY, 3: FireStamp.FIRE_SUIT}
def __init__(self, waddle): def __init__(self, waddle):
super().__init__(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): 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) await p.add_inventory(p.server.items[CardJitsuFireLogic.ItemAwards[rank]], notify=False)
if rank in CardJitsuFireLogic.StampAwards: 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( await p.update(
fire_ninja_rank=p.fire_ninja_rank + ranks fire_ninja_rank=p.fire_ninja_rank + ranks
).apply() ).apply()
@ -567,18 +577,18 @@ async def end_game_stamps(ninja, finish_position):
if finish_position == 1: if finish_position == 1:
await ninja.penguin.update(fire_matches_won=ninja.penguin.fire_matches_won + 1).apply() await ninja.penguin.update(fire_matches_won=ninja.penguin.fire_matches_won + 1).apply()
if ninja.penguin.fire_matches_won >= 10: 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: 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: 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: 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: 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: 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')) @handlers.handler(XTPacket('gz', ext='z'))
@ -665,5 +675,5 @@ async def handle_info_ready_sync(p):
@handlers.handler(XTPacket('lz', ext='z')) @handlers.handler(XTPacket('lz', ext='z'))
@handlers.player_in_room(CardJitsuFireLogic.room_id) @handlers.player_in_room(CardJitsuFireLogic.room_id)
async def handle_leave_game(p): async def handle_leave_game(p: Penguin):
await ninja_stamps_earned(p) await p.send_card_jitsu_stamp_info()

View File

@ -12,6 +12,17 @@ from houdini.handlers import XTPacket
from houdini.penguin import Penguin from houdini.penguin import Penguin
from houdini.data.ninja import Card 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 @dataclass
class WaterCard: class WaterCard:
@ -252,6 +263,13 @@ class WaterPlayer:
cleared: int = 0 cleared: int = 0
"""Number of stones cleared in the match""" """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: def get_card(self, hand_id: int) -> WaterCard:
"""Get the card given its hand ID""" """Get the card given its hand ID"""
return next((card for card in self.hand.cards if card.hand_id == hand_id), None) 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] ITEM_AWARDS = [6026, 4121, 2025, 1087, 3032]
"""All the items gained from ranking, indexed by their rank""" """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""" """Map rank and the stamp you gain from LEAVING the rank"""
board_cycle_handler: WaterCycleHandler board_cycle_handler: WaterCycleHandler
@ -469,6 +491,13 @@ class CardJitsuWaterLogic(IWaddle):
# original) # original)
self.board = Board(columns=5 if len(waddle.penguins) <= 2 else 7) self.board = Board(columns=5 if len(waddle.penguins) <= 2 else 7)
async def remove_penguin(self, p: Penguin):
player = self.get_player_by_penguin(p)
# this may run twice for the same player
if player is not None:
self.players[player.seat_id].left = True
await super().remove_penguin(p)
async def send_zm(self, *args): async def send_zm(self, *args):
"""Send a "zm" packet, used for various commands, to the clients""" """Send a "zm" packet, used for various commands, to the clients"""
await self.send_xt("zm", "&".join(map(str, args))) await self.send_xt("zm", "&".join(map(str, args)))
@ -477,9 +506,11 @@ class CardJitsuWaterLogic(IWaddle):
"""Send a "zm" packet, used for various commands, to a specific client""" """Send a "zm" packet, used for various commands, to a specific client"""
await player.penguin.send_xt("zm", "&".join(map(str, args))) await player.penguin.send_xt("zm", "&".join(map(str, args)))
def get_player_by_penguin(self, penguin: Penguin) -> WaterPlayer: def get_player_by_penguin(self, penguin: Penguin) -> Union[WaterPlayer, None]:
"""Get the player instance associated with a penguin""" """Get the player instance associated with a penguin"""
return next(player for player in self.players if player.penguin == penguin) return next(
(player for player in self.players if player.penguin == penguin), None
)
def get_card_generator(self, p: Penguin) -> Generator[WaterCard, None, None]: def get_card_generator(self, p: Penguin) -> Generator[WaterCard, None, None]:
"""Get a generator for a player's cards""" """Get a generator for a player's cards"""
@ -634,15 +665,12 @@ class CardJitsuWaterLogic(IWaddle):
).apply() ).apply()
if winner.penguin.water_matches_won >= 100: if winner.penguin.water_matches_won >= 100:
# Water Expert stamp await winner.penguin.add_card_jitsu_stamp(WaterStamp.WATER_EXPERT)
await winner.penguin.add_stamp(winner.penguin.server.stamps[276])
# Gong! stamp await winner.penguin.add_card_jitsu_stamp(WaterStamp.GONG)
await winner.penguin.add_stamp(winner.penguin.server.stamps[270])
if winner.two_close >= 2: if winner.two_close >= 2:
# Two Close stamp await winner.penguin.add_card_jitsu_stamp(WaterStamp.TWO_CLOSE)
await winner.penguin.add_stamp(winner.penguin.server.stamps[286])
# iterate over all players that drowned from the last place order # iterate over all players that drowned from the last place order
for row in self.board.rows: for row in self.board.rows:
@ -650,7 +678,7 @@ class CardJitsuWaterLogic(IWaddle):
break break
players_in_row = self.get_players_in_row(row) players_in_row = self.get_players_in_row(row)
for player in players_in_row: for player in players_in_row:
if isinstance(player, WaterSensei): if isinstance(player, WaterSensei) or player.left:
continue continue
# because winner has already been removed # because winner has already been removed
@ -684,7 +712,7 @@ class CardJitsuWaterLogic(IWaddle):
p.server.items[cls.ITEM_AWARDS[rank]], cost=0, notify=False p.server.items[cls.ITEM_AWARDS[rank]], cost=0, notify=False
) )
if rank in cls.STAMP_AWARDS: 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() await p.update(water_ninja_rank=p.water_ninja_rank + ranks).apply()
return True return True
@ -700,20 +728,28 @@ class CardJitsuWaterLogic(IWaddle):
players_in_row = self.get_players_in_row(drop_row) players_in_row = self.get_players_in_row(drop_row)
position = len(self.players) position = len(self.players)
for player in players_in_row: for player in players_in_row:
if player.penguin is not None: if not player.left:
# Watery Fall stamp # Watery Fall stamp
await player.penguin.add_stamp(player.penguin.server.stamps[274]) 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 # CMD_PLAYER_KILL, meant for players who lose from falling
player_kill_data = [] player_kill_data = []
for player in players_in_row: for player in players_in_row:
amulet = await self.update_player_progress( amulet = None
player, fell=True, position=position if not player.left:
) amulet = await self.update_player_progress(
player, fell=True, position=position
)
else:
amulet = Amulet(None, False)
player_kill_data.append( player_kill_data.append(
f"pk&{player.seat_id}&{position}&{amulet.serialize()}&false" f"pk&{player.seat_id}&{position}&{amulet.serialize()}&false"
) )
await self.send_zm(":".join(player_kill_data)) await self.send_zm(":".join(player_kill_data))
# for Two Close stamp # for Two Close stamp
@ -985,12 +1021,15 @@ def get_water_rank_threshold(rank):
async def handle_get_game(p: Penguin): async def handle_get_game(p: Penguin):
"""Handle the client entering the game""" """Handle the client entering the game"""
seat_id = p.waddle.get_seat_id(p) 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 # needs to send these or the client dies
await p.send_xt("gz") await p.send_xt("gz")
await p.send_xt("jz") 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 # CMD_PLAYER_INDEX
await p.waddle.send_zm_client(player, "po", seat_id) await p.waddle.send_zm_client(player, "po", seat_id)
@ -1109,8 +1148,7 @@ async def handle_throw_card(p: Penguin, *, cell_id: str):
) )
if player.cleared >= 28: if player.cleared >= 28:
# Skipping Stones stamp await p.add_card_jitsu_stamp(WaterStamp.SKIPPING_STONES)
await player.penguin.add_stamp(player.penguin.server.stamps[288])
# CMD_PLAYER_THROW # CMD_PLAYER_THROW
await p.waddle.send_zm( await p.waddle.send_zm(

View File

@ -244,6 +244,8 @@ class Penguin(Spheniscidae, penguin.Penguin):
if stamp.id in self.stamps: if stamp.id in self.stamps:
return False return False
await self.server.redis.set(self.get_recent_stamp_key(stamp.id), 1)
await self.stamps.insert(stamp_id=stamp.id) await self.stamps.insert(stamp_id=stamp.id)
if notify: if notify:
@ -254,6 +256,10 @@ class Penguin(Spheniscidae, penguin.Penguin):
return True return True
def get_recent_stamp_key(self, stamp_id):
"""Get redis key that locates the session recency of a stamp"""
return f'{self.id}.{stamp_id}.recentstamp'
async def add_inbox(self, postcard, sender_name="sys", sender_id=None, details=""): async def add_inbox(self, postcard, sender_name="sys", sender_id=None, details=""):
penguin_postcard = await PenguinPostcard.create(penguin_id=self.id, sender_id=sender_id, penguin_postcard = await PenguinPostcard.create(penguin_id=self.id, sender_id=sender_id,
postcard_id=postcard.id, details=details) postcard_id=postcard.id, details=details)
@ -381,6 +387,89 @@ class Penguin(Spheniscidae, penguin.Penguin):
self.logger.info(f'{self.username} updated their background to \'{item.name}\' ' if item else self.logger.info(f'{self.username} updated their background to \'{item.name}\' ' if item else
f'{self.username} removed their background item') 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]:
"""
Get the info of the stamps at the end of a game that the client requires
If clear_session is True, the stamps will be marked off and will no longer
show up as new stamps at the end of minigames
"""
(
collected_game_stamps_string,
total_collected_game_stamps,
total_game_stamps,
total_stamps,
) = ("", 0, 0, 0)
game_stamps = [
stamp
for stamp in self.server.stamps.values()
if stamp.group_id == self.room.stamp_group
]
game_stamps_ids = [stamp.id for stamp in game_stamps]
recently_collected_game_stamps = []
for stamp in self.stamps.values():
if stamp.stamp_id in game_stamps_ids:
is_recent = await self.server.redis.get(
self.get_recent_stamp_key(stamp.stamp_id)
)
if is_recent:
recently_collected_game_stamps.append(stamp)
collected_game_stamps = [
stamp for stamp in game_stamps if (stamp.id in self.stamps and stamp)
]
collected_game_stamps_string = "|".join(
str(stamp.stamp_id) for stamp in recently_collected_game_stamps
)
total_collected_game_stamps = len(collected_game_stamps)
total_game_stamps = len(game_stamps)
total_stamps = len(
[
stamp
for stamp in self.stamps.values()
if self.server.stamps[stamp.stamp_id].group_id
]
)
if clear_session:
await self.clear_stamps_session()
return (
collected_game_stamps_string,
total_collected_game_stamps,
total_game_stamps,
total_stamps,
)
async def clear_stamps_session(self):
"""
Exits a game session and unmarks all stamps since we are no longer in their session
"""
async for key in self.server.redis.scan_iter(self.get_recent_stamp_key("*")):
await self.server.redis.delete(key)
def __repr__(self): def __repr__(self):
if self.id is not None: if self.id is not None:
return f'<Penguin ID=\'{self.id}\' Username=\'{self.username}\'>' return f'<Penguin ID=\'{self.id}\' Username=\'{self.username}\'>'