diff --git a/.github/workflows/dev-image.yml b/.github/workflows/dev-image.yml index b936f61..e9d8ec4 100644 --- a/.github/workflows/dev-image.yml +++ b/.github/workflows/dev-image.yml @@ -5,6 +5,8 @@ on: jobs: build: runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: '1' steps: - uses: actions/checkout@v2 - name: Build the Unified Docker image diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 32d50b2..cb13728 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -5,6 +5,8 @@ on: jobs: build: runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: '1' steps: - uses: actions/checkout@v2 - name: Build the Unified Docker image diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c683393..d84f4b7 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -4,6 +4,8 @@ on: jobs: test_build: runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: '1' steps: - uses: actions/checkout@v2 - name: Build the Unified Docker image diff --git a/backend/__init__.py b/backend/__init__.py index 66b42d1..82c5a01 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -67,7 +67,7 @@ def get_me(sid, room): print('room exists') if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]): print('getting inside the bot') - bot = [p for p in de_games[0].players if p.is_bot][0] + bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0] bot.sid = sid bot.is_bot = False sio.enter_room(sid, de_games[0].name) @@ -82,6 +82,8 @@ def get_me(sid, room): sio.get_session(sid).game = de_games[0] sio.enter_room(sid, de_games[0].name) de_games[0].notify_room(sid) + de_games[0].notify_all() + de_games[0].notify_event_card() else: create_room(sid, room['name']) if sio.get_session(sid).game == None: @@ -171,18 +173,53 @@ def chat_message(sid, msg): elif '/suicide' in msg and ses.game.started and ses.lives > 0: ses.lives = 0 ses.notify_self() + elif '/nextevent' in msg and ses.game.started: + ses.game.deck.flip_event() + elif '/notify' in msg and ses.game.started: + cmd = msg.split() + if len(cmd) >= 3: + if cmd[1] in ses.game.players_map: + ses.game.get_player_named(cmd[1]).notify_card(ses, { + 'name': ' '.join(cmd[2:]), + 'icon': '🚨', + 'suit': 4, + 'number': ' '.join(cmd[2:]) + }) + else: + sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'}) + elif '/debug_show_cards' in msg and ses.game.started: + cmd = msg.split() + if len(cmd) == 2: + if cmd[1] in ses.game.players_map: + sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and is looking at {cmd[1]} hand'}) + for c in ses.game.get_player_named(cmd[1]).hand: + ses.notify_card(ses, c) + eventlet.sleep(0.3) + else: + sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'}) elif '/togglecomp' in msg and ses.game: ses.game.toggle_competitive() elif '/togglebot' in msg and ses.game: ses.game.toggle_disconnect_bot() elif '/cancelgame' in msg and ses.game.started: ses.game.reset() + elif '/startgame' in msg and not ses.game.started: + ses.game.start_game() + elif '/addex' in msg and not ses.game.started: + cmd = msg.split() + if len(cmd) == 2: + cmd[1] = cmd[1].replace('foc', 'fistful_of_cards') + if cmd[1] not in ses.game.available_expansions: + ses.game.available_expansions.append(cmd[1]) + ses.game.notify_room() + else: + sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} bad format'}) elif '/gameinfo' in msg: - sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.game.__dict__}'}) + sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.game.__dict__}'}) elif '/meinfo' in msg: - sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'info: {ses.__dict__}'}) + sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'}) else: - sio.emit('chat_message', room=sid, data={'color': f'#black','text':f'{msg} COMMAND NOT FOUND'}) + sio.emit('chat_message', room=sid, data={'color': f'','text':f'{msg} COMMAND NOT FOUND'}) else: color = sid.encode('utf-8').hex()[-3:] sio.emit('chat_message', room=ses.game.name, data={'color': f'#{color}','text':f'[{ses.name}]: {msg}'}) @@ -238,5 +275,15 @@ def scrap(sid, card_index): ses: Player = sio.get_session(sid) ses.scrap(card_index) +@sio.event +def chuck_lose_hp_draw(sid): + ses: Player = sio.get_session(sid) + ses.chuck_lose_hp_draw() + +@sio.event +def holyday_special(sid, data): + ses: Player = sio.get_session(sid) + ses.holyday_special(data) + if __name__ == '__main__': eventlet.wsgi.server(eventlet.listen(('', 5001)), app) diff --git a/backend/bang/cards.py b/backend/bang/cards.py index 4a664bc..91faeac 100644 --- a/backend/bang/cards.py +++ b/backend/bang/cards.py @@ -180,7 +180,8 @@ class Bang(Card): self.need_target = True def play_card(self, player, against, _with=None): - if player.has_played_bang and not any([isinstance(c, Volcanic) for c in player.equipment]) and against != None: + import bang.expansions.fistful_of_cards.card_events as ce + if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None: return False elif against != None: import bang.characters as chars diff --git a/backend/bang/characters.py b/backend/bang/characters.py index 3356c31..ea6c0ce 100644 --- a/backend/bang/characters.py +++ b/backend/bang/characters.py @@ -137,7 +137,7 @@ class VultureSam(Character): super().__init__("Vulture Sam", max_lives=4) self.desc = "Quando un personaggio viene eliminato prendi tutte le carte di quel giocatore e aggiungile alla tua mano, sia le carte in mano che quelle in gioco" self.desc_eng = "When a player dies, he gets all the cards in the dead's hand and equipments" - self.icon = '💰' + self.icon = '🦉' class WillyTheKid(Character): def __init__(self): diff --git a/backend/bang/deck.py b/backend/bang/deck.py index 88bb6ab..f484e8b 100644 --- a/backend/bang/deck.py +++ b/backend/bang/deck.py @@ -1,6 +1,7 @@ from typing import List, Set, Dict, Tuple, Optional import random import bang.cards as cs +import bang.expansions.fistful_of_cards.card_events as ce class Deck: def __init__(self, game): @@ -18,10 +19,19 @@ class Deck: if c.name not in self.all_cards_str: self.all_cards_str.append(c.name) self.game = game + self.event_cards: List[ce.CardEvent] = [] + if 'fistful_of_cards' in game.expansions: + self.event_cards.extend(ce.get_all_events()) + random.shuffle(self.event_cards) random.shuffle(self.cards) self.scrap_pile: List[cs.Card] = [] print(f'Deck initialized with {len(self.cards)} cards') + def flip_event(self): + if len(self.event_cards) > 0: + self.event_cards.append(self.event_cards.pop(0)) + self.game.notify_event_card() + def peek(self, n_cards: int) -> list: return self.cards[:n_cards] diff --git a/backend/bang/expansions/__init__.py b/backend/bang/expansions/__init__.py index 4409025..eab027e 100644 --- a/backend/bang/expansions/__init__.py +++ b/backend/bang/expansions/__init__.py @@ -1,7 +1,9 @@ -from bang.expansions.dodge_city import cards, characters class DodgeCity(): def get_characters(): + from bang.expansions.dodge_city import characters return characters.all_characters() + def get_cards(): - return cards.get_starting_deck() \ No newline at end of file + from bang.expansions.dodge_city import cards + return cards.get_starting_deck() diff --git a/backend/bang/expansions/dodge_city/characters.py b/backend/bang/expansions/dodge_city/characters.py index 9a23a03..4655a1d 100644 --- a/backend/bang/expansions/dodge_city/characters.py +++ b/backend/bang/expansions/dodge_city/characters.py @@ -71,6 +71,41 @@ class BelleStar(Character): self.desc_eng = "During her turn the green cards of the other players do not work." self.icon = '❎' +class VeraCuster(Character): + def __init__(self): + super().__init__("Vera Custer", max_lives=3) + self.desc = "Prima di pescare le sue carte può scegliere l'abilità speciale di un altro giocatore fino al prossimo turno." + self.desc_eng = "Before drawing, she may choose the special ability on another alive player. This ability is used until next turn." + self.icon = '🎭' + +class ChuckWengam(Character): + def __init__(self): + super().__init__("Chuck Wengam", max_lives=4) + self.desc = "Durante il suo turno può perdere una vita per pescare 2 carte dal mazzo." + self.desc_eng = "On his turn he may decide to lose 1 HP to draw 2 cards from the deck." + self.icon = '💰' + +class PatBrennan(Character): + def __init__(self): + super().__init__("Pat Brennan", max_lives=4) + self.desc = "Invece di pescare può prendere una carta dall'equipaggiamento di un altro giocatore." + self.desc_eng = "Instead of drawing he can steal a card from the equipment of another player." + self.icon = '🤗' + +class JoseDelgrado(Character): + def __init__(self): + super().__init__("José Delgrado", max_lives=4) + self.desc = "Può scartare una carta blu per pescare 2 carte." + self.desc_eng = "He can discard a blue card to draw 2 cards." + self.icon = '🎒' + +class DocHolyday(Character): + def __init__(self): + super().__init__("Doc Holyday", max_lives=4) + self.desc = "Nel suo turno può scartare 2 carte per fare un bang." + self.desc_eng = "He can discard 2 cards to play a bang." + self.icon = '✌🏻' + def all_characters() -> List[Character]: return [ PixiePete(), @@ -83,6 +118,11 @@ def all_characters() -> List[Character]: ApacheKid(), SeanMallory(), BelleStar(), + VeraCuster(), + ChuckWengam(), + PatBrennan(), + JoseDelgrado(), + DocHolyday(), ] #Apache Kid: il suo effetto non conta nei duelli diff --git a/backend/bang/expansions/fistful_of_cards/card_events.py b/backend/bang/expansions/fistful_of_cards/card_events.py new file mode 100644 index 0000000..528d3b3 --- /dev/null +++ b/backend/bang/expansions/fistful_of_cards/card_events.py @@ -0,0 +1,115 @@ +from abc import ABC, abstractmethod + +class CardEvent(ABC): + def __init__(self, name, icon): + self.name = name + self.icon = icon + +class Agguato(CardEvent): + def __init__(self): + super().__init__('Agguato', '🛁') + self.desc = 'La distanza base di tra 2 qualsiasi giocatori è 1' + self.desc_eng = 'The base distance from any 2 players is 1' + +class Cecchino(CardEvent): + def __init__(self): #TODO + super().__init__('Cecchino', '👁') + self.desc = 'Nel proprio turno i giocatori possono scartare 2 Bang assieme per sparare un bang a cui servono 2 mancato' + self.desc_eng = 'During their turn, players can discard 2 Bang! to shoot a bang that requires 2 missed' + +class DeadMan(CardEvent): + def __init__(self): + super().__init__('Dead Man', '⚰️') + self.desc = 'Al proprio turno il giocatore che è morto per primo torna in vita con 2 vite e 2 carte' + self.desc_eng = 'The first player that died return back to life with 2 hp and 2 cards' + +class FratelliDiSangue(CardEvent): + def __init__(self):#TODO + super().__init__('Fratelli Di Sangue', '💉') + self.desc = 'All\'inizio del proprio turno i giocatori possono perdere 1 vita per darla a un altro giocatore' + self.desc_eng = 'At the begin of their turn, payers can lose 1 hp to give it to another player' + +class IlGiudice(CardEvent): + def __init__(self): + super().__init__('Il Giudice', '👨‍⚖️') + self.desc = 'Non si possono equipaggiare carte a se stessi o agli altri' + self.desc_eng = 'You can\'t equip cards on your or other players' + +class Lazo(CardEvent): + def __init__(self): + super().__init__('Lazo', '📿') + self.desc = 'Le carte equipaggiate non hanno effetto' + self.desc_eng = 'Cards in the equipment slot do not work' + +class LeggeDelWest(CardEvent): + def __init__(self): + super().__init__('Legge Del West', '⚖️') + self.desc = 'I giocatori mostrano la seconda carta che pescano e sono obbligati a usarla in quel turno (se possibile)' + self.desc_eng = 'Every player shows the second card that they draw and must use it in that round' + +class LiquoreForte(CardEvent): + def __init__(self):#TODO + super().__init__('Liquore Forte', '🥃') + self.desc = 'I giocatori possono evitare di pescare per recuperare 1 vita' + self.desc_eng = 'Players can skip drawing to regain 1 HP' + +class MinieraAbbandonata(CardEvent): + def __init__(self):#TODO + super().__init__('Miniera Abbandonata', '⛏') + self.desc = 'I giocatori pescano dagli scarti e scartano in cima al mazzo' + self.desc_eng = 'Players draw from the discarded pile and discard to the deck' + +class PerUnPugnoDiCarte(CardEvent): + def __init__(self):#TODO + super().__init__('Per Un Pugno Di Carte', '🎴') + self.desc = 'Il giocatore subisce tanti bang quante carte ha in mano' + self.desc_eng = 'On his turn the player is target of as many Bang as how many cards he has in his hand' + +class Peyote(CardEvent): + def __init__(self):#TODO + super().__init__('Peyote', '🌵') + self.desc = 'Invece che pescare il giocatore prova a indovinare il colore del seme, se lo indovina continua' + self.desc_eng = 'Instead of drawing, the player tries to guess the color of the suit, if he\'s right he repeats' + +class Ranch(CardEvent): + def __init__(self):#TODO + super().__init__('Ranch', '🐮') + self.desc = 'Dopo aver pescato il giocatore può scartare quante carte vuole dalla mano e pescarne altrettante dal mazzo' + self.desc_eng = 'After drawing, the player can discard as many cards as he wants from his hand and draw as many from the deck' + +class Rimbalzo(CardEvent): + def __init__(self):#TODO + super().__init__('Rimbalzo', '⏮') + self.desc = 'Il giocatore di turno può giocare bang contro le carte equipaggiate dagli altri giocatori, se non giocano mancato vengono scartate' + self.desc_eng = 'The player can play bang against the cards equipped by the other players, if they do not play miss they are discarded' + +class RouletteRussa(CardEvent): + def __init__(self):#TODO + super().__init__('Roulette Russa', '🇷🇺') + self.desc = 'A partire dallo sceriffo, ogni giocatore scarta 1 mancato, il primo che non lo fa perde 2 vite' + self.desc_eng = 'Starting from the sheriff, every player discards 1 missed, the first one that doesn\'t loses 2 HP' + +class Vendetta(CardEvent): + def __init__(self): + super().__init__('Vendetta', '😤') + self.desc = 'Alla fine del proprio turno il giocatore estrae, se esce ♥️ gioca un altro turno' + self.desc_eng = 'When ending the turn, the player flips a card, if it\'s ♥️ he plays another turn' + +def get_all_events(): + return [ + Agguato(), + # Cecchino(), + DeadMan(), + # FratelliDiSangue(), + IlGiudice(), + Lazo(), + LeggeDelWest(), + # LiquoreForte(), + # MinieraAbbandonata(), + # PerUnPugnoDiCarte(), + # Peyote(), + # Ranch(), + # Rimbalzo(), + # RouletteRussa(), + Vendetta(), + ] \ No newline at end of file diff --git a/backend/bang/game.py b/backend/bang/game.py index 780bd2f..6057456 100644 --- a/backend/bang/game.py +++ b/backend/bang/game.py @@ -6,6 +6,7 @@ import bang.players as players import bang.characters as characters from bang.deck import Deck import bang.roles as roles +import bang.expansions.fistful_of_cards.card_events as ce import eventlet class Game: @@ -23,6 +24,7 @@ class Game: self.initial_players = 0 self.password = '' self.expansions = [] + self.available_expansions = ['dodge_city'] self.shutting_down = False self.is_competitive = False self.disconnect_bot = True @@ -37,6 +39,7 @@ class Game: 'is_competitive': self.is_competitive, 'disconnect_bot': self.disconnect_bot, 'expansions': self.expansions, + 'available_expansions': self.available_expansions }) def toggle_expansion(self, expansion_name): @@ -90,7 +93,11 @@ class Game: for k in range(self.players[i].max_lives): self.players[i].hand.append(self.deck.draw()) self.players[i].notify_self() - self.players[self.turn].play_turn() + current_roles = [type(x.role).__name__ for x in self.players] + random.shuffle(current_roles) + current_roles = str({x:current_roles.count(x) for x in current_roles}).replace('{','').replace('}','') + self.sio.emit('chat_message', room=self.name, data=f'_allroles|{current_roles}') + self.play_turn() def choose_characters(self): char_cards = random.sample(characters.all_characters(self.expansions), len(self.players)*2) @@ -121,6 +128,8 @@ class Game: elif len(self.players) >= 4: available_roles = [roles.Sheriff(), roles.Renegade(), roles.Outlaw(), roles.Outlaw(), roles.Vice(), roles.Outlaw(), roles.Vice(), roles.Renegade(), roles.Outlaw(), roles.Vice(), roles.Outlaw()] available_roles = available_roles[:len(self.players)] + else: + available_roles = [roles.Renegade(), roles.Renegade()] random.shuffle(available_roles) for i in range(len(self.players)): self.players[i].set_role(available_roles[i]) @@ -209,6 +218,15 @@ class Game: return self.players[(self.turn + 1) % len(self.players)] def play_turn(self): + if isinstance(self.players[self.turn].role, roles.Sheriff): + self.deck.flip_event() + if self.check_event(ce.DeadMan) and len(self.dead_players) > 0: + self.players.append(self.dead_players.pop(0)) + self.players[-1].lives = 2 + self.players[-1].hand.append(self.deck.draw()) + self.players[-1].hand.append(self.deck.draw()) + self.players_map = {c.name: i for i, c in enumerate(self.players)} + self.players[-1].notify_self() self.players[self.turn].play_turn() def next_turn(self): @@ -217,6 +235,10 @@ class Game: self.turn = (self.turn + 1) % len(self.players) self.play_turn() + def notify_event_card(self): + if len(self.deck.event_cards) > 0: + self.sio.emit('event_card', room=self.name, data=self.deck.event_cards[0].__dict__) + def notify_scrap_pile(self): print('scrap') if self.deck.peek_scrap_pile(): @@ -229,6 +251,8 @@ class Game: if player in self.players: if self.disconnect_bot and self.started: player.is_bot = True + eventlet.sleep(15) # he may reconnect + player.notify_self() else: self.player_death(player=player, disconnected=True) else: @@ -329,12 +353,17 @@ class Game: eventlet.sleep(0.5) self.notify_room() + def check_event(self, ev): + if len(self.deck.event_cards) == 0: return False + return isinstance(self.deck.event_cards[0], ev) + def get_visible_players(self, player: players.Player): i = self.players.index(player) sight = player.get_sight() + mindist = 99 if not self.check_event(ce.Agguato) else 1 return [{ 'name': self.players[j].name, - 'dist': min(abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players)))) + self.players[j].get_visibility() - (player.get_sight(countWeapon=False)-1), + 'dist': min([abs(i - j), (i+ abs(j-len(self.players))), (j+ abs(i-len(self.players))), mindist]) + self.players[j].get_visibility() - (player.get_sight(countWeapon=False)-1), 'lives': self.players[j].lives, 'max_lives': self.players[j].max_lives, 'is_sheriff': isinstance(self.players[j].role, roles.Sheriff), @@ -352,6 +381,7 @@ class Game: 'is_my_turn': p.is_my_turn, 'pending_action': p.pending_action, 'character': p.character.__dict__ if p.character else None, + 'real_character': p.real_character.__dict__ if p.real_character else None, 'icon': p.role.icon if self.initial_players == 3 and p.role else '🤠' } for p in self.players] self.sio.emit('players_update', room=self.name, data=data) diff --git a/backend/bang/players.py b/backend/bang/players.py index e90f2e9..2cea122 100644 --- a/backend/bang/players.py +++ b/backend/bang/players.py @@ -8,6 +8,7 @@ import bang.cards as cs import bang.expansions.dodge_city.cards as csd import bang.characters as chars import bang.expansions.dodge_city.characters as chd +import bang.expansions.fistful_of_cards.card_events as ce import eventlet class PendingAction(IntEnum): @@ -30,6 +31,7 @@ class Player: self.equipment: cs.Card = [] self.role: r.Role = None self.character: chars.Character = None + self.real_character: chars.Character = None self.lives = 0 self.max_lives = 0 self.game: g = None @@ -49,12 +51,14 @@ class Player: self.mancato_needed = 0 self.molly_discarded_cards = 0 self.is_bot = bot + self.special_use_count = 0 def reset(self): self.hand: cs.Card = [] self.equipment: cs.Card = [] self.role: r.Role = None self.character: chars.Character = None + self.real_character: chars.Character = None self.lives = 0 self.max_lives = 0 self.is_my_turn = False @@ -70,6 +74,7 @@ class Player: self.attacker: Player = None self.target_p: str = None self.is_drawing = False + self.special_use_count = 0 try: del self.win_status except: @@ -92,13 +97,24 @@ class Player: def set_character(self, character: str): print(self.available_characters, character) - self.character = next( - x for x in self.available_characters if x.name == character) - self.available_characters = [] - print(f'I {self.name} chose character {self.character.name}') - self.sio.emit('chat_message', room=self.game.name, - data=f'_did_choose_character|{self.name}') - self.game.notify_character_selection() + if self.character == None: + self.character = next( + x for x in self.available_characters if x.name == character) + self.real_character = self.character + self.available_characters = [] + print(f'I {self.name} chose character {self.character.name}') + self.sio.emit('chat_message', room=self.game.name, + data=f'_did_choose_character|{self.name}') + self.game.notify_character_selection() + elif self.real_character and isinstance(self.real_character, chd.VeraCuster): + self.character = next( + x for x in self.available_characters if x.name == character) + self.available_characters = [] + self.sio.emit('chat_message', room=self.game.name, + data=f'_did_choose_character|{self.name}') + self.pending_action = PendingAction.DRAW + self.notify_self() + def prepare(self): self.max_lives = self.character.max_lives + self.role.health_mod @@ -116,10 +132,15 @@ class Player: else: self.set_character(available[randrange(0, len(available))].name) - def notify_card(self, player, card): + def notify_card(self, player, card, message=''): + try: + card = card.__dict__ + except: + pass mess = { 'player': player.name, - 'card': card.__dict__ + 'card': card, + 'message':message } print('notifying card') self.sio.emit('notify_card', room=self.sid, data=mess) @@ -184,7 +205,7 @@ class Player: self.draw('') elif self.pending_action == PendingAction.PLAY: has_played = False - if len([c for c in self.hand if c.is_equipment or c.usable_next_turn]) > 0: + if len([c for c in self.hand if (c.is_equipment or c.usable_next_turn) and not self.game.check_event(ce.IlGiudice)]) > 0: for i in range(len(self.hand)): if self.hand[i].is_equipment or self.hand[i].usable_next_turn: self.play_card(i) @@ -246,7 +267,7 @@ class Player: did_respond = True break for i in range(len(self.equipment)): - if self.equipment[i].name in self.expected_response: + if not self.game.check_event(ce.Lazo) and self.equipment[i].name in self.expected_response: self.respond(len(self.hand)+i) did_respond = True break @@ -274,10 +295,14 @@ class Player: self.is_my_turn = True self.is_waiting_for_action = True self.has_played_bang = False - if any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): + self.special_use_count = 0 + if not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): self.pending_action = PendingAction.PICK else: - self.pending_action = PendingAction.DRAW + if isinstance(self.real_character, chd.VeraCuster): + self.set_available_character([p.character for p in self.game.players if p != self]) + else: + self.pending_action = PendingAction.DRAW self.notify_self() def draw(self, pile): @@ -288,6 +313,11 @@ class Player: self.available_cards = [self.game.deck.draw() for i in range(3)] self.pending_action = PendingAction.CHOOSE self.notify_self() + elif isinstance(self.character, chd.PatBrennan) and type(pile) == str and pile != self.name and pile in self.game.players_map and len(self.game.get_player_named(pile).equipment) > 0: + self.is_drawing = True + self.available_cards = self.game.get_player_named(pile).equipment + self.pending_action = PendingAction.CHOOSE + self.notify_self() else: self.pending_action = PendingAction.PLAY if pile == 'scrap' and isinstance(self.character, chars.PedroRamirez): @@ -310,11 +340,11 @@ class Player: for i in range(2): card: cs.Card = self.game.deck.draw() self.hand.append(card) - if i == 1 and isinstance(self.character, chars.BlackJack): + if i == 1 and isinstance(self.character, chars.BlackJack) or self.game.check_event(ce.LeggeDelWest): for p in self.game.players: if p != self: - p.notify_card(self, card) - if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS: + p.notify_card(self, card, 'blackjack_special' if isinstance(self.character, chars.BlackJack) else 'foc.leggedelwest') + if card.suit == cs.Suit.HEARTS or card.suit == cs.Suit.DIAMONDS and isinstance(self.character, chars.BlackJack): self.hand.append(self.game.deck.draw()) if isinstance(self.character, chd.PixiePete): self.hand.append(self.game.deck.draw()) @@ -371,7 +401,10 @@ class Player: if any([isinstance(c, cs.Prigione) for c in self.equipment]): self.notify_self() return - self.pending_action = PendingAction.DRAW + if isinstance(self.real_character, chd.VeraCuster): + self.set_available_character([p.character for p in self.game.players if p != self]) + else: + self.pending_action = PendingAction.DRAW self.notify_self() else: self.pending_action = PendingAction.WAIT @@ -405,9 +438,10 @@ class Player: withCard = self.hand.pop(_with) if hand_index > _with else self.hand.pop(_with - 1) print(self.name, 'is playing ', card, ' against:', against, ' with:', _with) did_play_card = False - if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.suit == cs.Suit.DIAMONDS): + event_blocks_card = (self.game.check_event(ce.IlGiudice) and (card.is_equipment or (card.usable_next_turn and not card.can_be_used_now))) or (self.game.check_event(ce.Lazo) and card.usable_next_turn and card.can_be_used_now) + if not(against != None and isinstance(self.game.get_player_named(against).character, chd.ApacheKid) and card.suit == cs.Suit.DIAMONDS) and not event_blocks_card: did_play_card = card.play_card(self, against, withCard) - if not card.is_equipment and not card.usable_next_turn: + if not card.is_equipment and not card.usable_next_turn or event_blocks_card: if did_play_card: self.game.deck.scrap(card) else: @@ -456,6 +490,14 @@ class Player: self.is_drawing = False self.pending_action = PendingAction.PLAY self.notify_self() + elif self.is_drawing and isinstance(self.character, chd.PatBrennan): + card = self.available_cards.pop(card_index) + if card.usable_next_turn: + card.can_be_used_now = False + self.hand.append(card) + self.available_cards = [] + self.pending_action = PendingAction.PLAY + self.notify_self() else: # emporio self.game.respond_emporio(self, card_index) @@ -500,7 +542,7 @@ class Player: self.take_damage_response() return False else: - if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 or isinstance(self.character, chars.Jourdonnais): + if (not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) or isinstance(self.character, chars.Jourdonnais): print('has barrel') self.pending_action = PendingAction.PICK self.on_pick_cb = self.barrel_pick @@ -508,7 +550,7 @@ class Player: print('has mancato') self.pending_action = PendingAction.RESPOND self.expected_response = self.game.deck.mancato_cards - if self.attacker and isinstance(self.attacker.character, chd.BelleStar): + if self.attacker and isinstance(self.attacker.character, chd.BelleStar) or self.game.check_event(ce.Lazo): self.expected_response = self.game.deck.mancato_cards_not_green if isinstance(self.character, chd.ElenaFuente): self.expected_response = self.game.deck.all_cards_str @@ -621,6 +663,8 @@ class Player: range += card.range else: aim += card.sight_mod + if self.game.check_event(ce.Lazo): + return 1 + self.character.sight_mod return max(1, range) + aim + self.character.sight_mod def get_visibility(self): @@ -629,15 +673,38 @@ class Player: covers = 0 for card in self.equipment: covers += card.vis_mod + if self.game.check_event(ce.Lazo): + return self.character.visibility_mod return self.character.visibility_mod + covers def scrap(self, card_index): if self.is_my_turn or isinstance(self.character, chars.SidKetchum): self.scrapped_cards += 1 + card = self.hand.pop(card_index) if isinstance(self.character, chars.SidKetchum) and self.scrapped_cards == 2: self.scrapped_cards = 0 self.lives = min(self.lives+1, self.max_lives) - self.game.deck.scrap(self.hand.pop(card_index)) + elif isinstance(self.character, chd.JoseDelgrado) and card.is_equipment and self.special_use_count < 2: + self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw()) + self.special_use_count += 1 + self.game.deck.scrap(card) + self.notify_self() + + def holyday_special(self, data): + if isinstance(self.character, chd.DocHolyday) and self.special_use_count < 1: + self.special_use_count += 1 + cards = sorted(data['cards'], reverse=True) + for c in cards: + self.game.deck.scrap(self.hand.pop(c)) + self.notify_self() + self.game.attack(self, data['against']) + + def chuck_lose_hp_draw(self): + if isinstance(self.character, chd.ChuckWengam) and self.lives > 1 and self.is_my_turn: + self.lives -= 1 + self.hand.append(self.game.deck.draw()) + self.hand.append(self.game.deck.draw()) self.notify_self() def end_turn(self, forced=False): @@ -648,6 +715,12 @@ class Player: print( f"I {self.name} have to many cards in my hand and I can't end the turn") elif self.pending_action == PendingAction.PLAY or forced: + if not forced and self.game.check_event(ce.Vendetta): + picked: cs.Card = self.game.deck.pick_and_scrap() + self.sio.emit('chat_message', room=self.game.name, data=f'_flipped|{self.name}|{picked}') + if picked.suit == cs.Suit.HEARTS: + self.play_turn() + return self.is_my_turn = False for i in range(len(self.equipment)): if self.equipment[i].usable_next_turn and not self.equipment[i].can_be_used_now: diff --git a/frontend/src/assets/logo.png b/frontend/src/assets/logo.png deleted file mode 100644 index f3d2503..0000000 Binary files a/frontend/src/assets/logo.png and /dev/null differ diff --git a/frontend/src/components/Card.vue b/frontend/src/components/Card.vue index 3b95b94..65a339f 100644 --- a/frontend/src/components/Card.vue +++ b/frontend/src/components/Card.vue @@ -3,7 +3,7 @@

{{card.name}}

{{card.icon}}
{{card.alt_text}}
-
{{card.number}}{{suit}}
+
{{number}}{{suit}}
@@ -20,6 +20,13 @@ export default { return x[this.card.suit]; } return ''; + }, + number() { + if (this.card.number === 1) return 'A' + else if (this.card.number === 11) return 'J' + else if (this.card.number === 12) return 'Q' + else if (this.card.number === 13) return 'K' + else return this.card.number } } } @@ -72,6 +79,14 @@ export default { box-shadow: 0 0 0 3pt #6aa16e, 0 0 0 6pt white, 0 0 5pt 6pt #aaa } +.card.high-noon{ + box-shadow: 0 0 0pt 4pt white, 0 0 5pt 4pt #aaa; + border: 2pt dotted rgb(198 78 45); +} +.card.fistful-of-cards{ + box-shadow: 0 0 0pt 4pt white, 0 0 5pt 4pt #aaa; + border: 2pt dashed rgb(50 122 172); +} .card h4 { position: absolute; text-align: center; @@ -85,6 +100,10 @@ export default { font-size:26pt; top: 35%; } +.fistful-of-cards .emoji, .high-noon .emoji{ + top:auto !important; + bottom:15% !important; +} .card .suit { position: absolute; bottom: 3pt; @@ -135,5 +154,11 @@ export default { box-shadow: 0 0 0 3pt #6aa16e, 0 0 0 6pt #181a1b, 0 0 5pt 6pt #aaa } + .card.high-noon{ + box-shadow: 0 0 0pt 4pt #181a1b, 0 0 5pt 4pt #aaa; + } + .card.fistful-of-cards{ + box-shadow: 0 0 0pt 4pt #181a1b, 0 0 5pt 4pt #aaa; + } } \ No newline at end of file diff --git a/frontend/src/components/Deck.vue b/frontend/src/components/Deck.vue index 649cb1a..16766a3 100644 --- a/frontend/src/components/Deck.vue +++ b/frontend/src/components/Deck.vue @@ -2,6 +2,11 @@
+
+
+
+ +
@@ -15,6 +20,7 @@
+

{{($i18n.locale=='it'?eventCard.desc:eventCard.desc_eng)}}

{{desc}}

@@ -37,6 +43,7 @@ export default { icon: '💥', }, lastScrap: null, + eventCard: null, previousScrap: null, pending_action: false, isPlaying: true, @@ -50,7 +57,10 @@ export default { }, scrap(card) { this.lastScrap = card - } + }, + event_card(card) { + this.eventCard = card + }, }, computed: { endTurnCard() { @@ -81,10 +91,10 @@ export default {