Merge branch 'main' into dev

This commit is contained in:
Giulio 2023-01-10 18:00:43 +01:00
commit 5a309aeac5
29 changed files with 8941 additions and 6389 deletions

View File

@ -3,7 +3,7 @@ on:
pull_request: pull_request:
jobs: jobs:
buildx: build-amd64:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- -
@ -31,6 +31,24 @@ jobs:
--output "type=image,push=false" \ --output "type=image,push=false" \
--tag albertoxamin/bang:test \ --tag albertoxamin/bang:test \
--file ./Dockerfile ./ --file ./Dockerfile ./
build-arm64:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: crazy-max/ghaction-docker-buildx@v3
-
name: Cache Docker layers
uses: actions/cache@v3
id: cache
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- -
name: Docker Buildx (test build arm64) name: Docker Buildx (test build arm64)
run: | run: |
@ -41,3 +59,31 @@ jobs:
--output "type=image,push=false" \ --output "type=image,push=false" \
--tag albertoxamin/bang:test \ --tag albertoxamin/bang:test \
--file ./Dockerfile ./ --file ./Dockerfile ./
build-armv-7:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up Docker Buildx
uses: crazy-max/ghaction-docker-buildx@v3
-
name: Cache Docker layers
uses: actions/cache@v3
id: cache
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
-
name: Docker Buildx (test build armv-7)
run: |
docker buildx build \
--cache-from "type=local,src=/tmp/.buildx-cache" \
--cache-to "type=local,dest=/tmp/.buildx-cache" \
--platform linux/arm/v7 \
--output "type=image,push=false" \
--tag albertoxamin/bang:test \
--file ./Dockerfile ./

View File

@ -216,7 +216,7 @@ class Bang(Card):
def play_card(self, player, against, _with=None): def play_card(self, player, against, _with=None):
if player.game.check_event(ceh.Sermone) and not self.number == 42: # 42 gold rush if player.game.check_event(ceh.Sermone) and not self.number == 42: # 42 gold rush
return False return False
if ((player.has_played_bang and not self.number == 42) and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)) and against != None): # 42 gold rush: if ((player.has_played_bang and not self.number == 42) and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)) and against != None): # 42 gold rush:
return False return False
elif against != None: elif against != None:
import bang.characters as chars import bang.characters as chars
@ -377,7 +377,7 @@ class Mancato(Card):
def play_card(self, player, against, _with=None): def play_card(self, player, against, _with=None):
import bang.characters as chars import bang.characters as chars
if against != None and player.character.check(player.game, chars.CalamityJanet): if against != None and player.character.check(player.game, chars.CalamityJanet):
if player.has_played_bang and (not any([isinstance(c, Volcanic) for c in player.equipment]) or player.game.check_event(ce.Lazo)): if player.has_played_bang and (not any((isinstance(c, Volcanic) for c in player.equipment)) or player.game.check_event(ce.Lazo)):
return False return False
if player.game.check_event(ceh.Sermone): if player.game.check_event(ceh.Sermone):
return False return False
@ -445,7 +445,7 @@ class WellsFargo(Card):
def get_starting_deck(expansions:List[str]) -> List[Card]: def get_starting_deck(expansions:List[str]) -> List[Card]:
from bang.expansions import DodgeCity from bang.expansions import DodgeCity, TheValleyOfShadows
base_cards = [ base_cards = [
Barile(Suit.SPADES, 'Q'), Barile(Suit.SPADES, 'Q'),
Barile(Suit.SPADES, 'K'), Barile(Suit.SPADES, 'K'),
@ -530,5 +530,7 @@ def get_starting_deck(expansions:List[str]) -> List[Card]:
] ]
if 'dodge_city' in expansions: if 'dodge_city' in expansions:
base_cards.extend(DodgeCity.get_cards()) base_cards.extend(DodgeCity.get_cards())
if 'the_valley_of_shadows' in expansions:
base_cards.extend(TheValleyOfShadows.get_cards())
return base_cards return base_cards

View File

@ -9,6 +9,7 @@ class Character(ABC):
self.max_lives = max_lives self.max_lives = max_lives
self.sight_mod = sight_mod self.sight_mod = sight_mod
self.visibility_mod = visibility_mod self.visibility_mod = visibility_mod
self.is_character = True
self.pick_mod = pick_mod self.pick_mod = pick_mod
self.desc = desc self.desc = desc
self.icon = '🤷‍♂️' self.icon = '🤷‍♂️'
@ -145,7 +146,7 @@ class WillyTheKid(Character):
self.icon = '🎉' self.icon = '🎉'
def all_characters(expansions: List[str]): def all_characters(expansions: List[str]):
from bang.expansions import DodgeCity from bang.expansions import DodgeCity, TheValleyOfShadows
base_chars = [ base_chars = [
BartCassidy(), BartCassidy(),
BlackJack(), BlackJack(),
@ -168,4 +169,6 @@ def all_characters(expansions: List[str]):
base_chars.extend(DodgeCity.get_characters()) base_chars.extend(DodgeCity.get_characters())
if 'gold_rush' in expansions: if 'gold_rush' in expansions:
base_chars.extend(GoldRush.get_characters()) base_chars.extend(GoldRush.get_characters())
if 'the_valley_of_shadows' in expansions:
base_chars.extend(TheValleyOfShadows.get_characters())
return base_chars return base_chars

View File

@ -52,7 +52,7 @@ class Deck:
self.game.notify_event_card() self.game.notify_event_card()
def fill_gold_rush_shop(self): def fill_gold_rush_shop(self):
if not any([c == None for c in self.shop_cards]): if not any((c == None for c in self.shop_cards)):
return return
for i in range(3): for i in range(3):
if self.shop_cards[i] == None: if self.shop_cards[i] == None:

View File

@ -13,3 +13,12 @@ class GoldRush():
def get_characters(): def get_characters():
from bang.expansions.gold_rush import characters from bang.expansions.gold_rush import characters
return characters.all_characters() return characters.all_characters()
class TheValleyOfShadows():
def get_characters():
from bang.expansions.the_valley_of_shadows import characters
return characters.all_characters()
def get_cards():
from bang.expansions.the_valley_of_shadows import cards
return cards.get_starting_deck()

View File

@ -75,7 +75,7 @@ class Rissa(CatBalou):
def play_card(self, player, against, _with): def play_card(self, player, against, _with):
if _with != None: if _with != None:
if len([p for p in player.game.players if p != player and (len(p.hand)+len(p.equipment)) > 0]) == 0: if not any((p != player and (len(p.hand)+len(p.equipment)) > 0 for p in player.game.players)):
return False return False
#se sono qui vuol dire che ci sono giocatori con carte in mano oltre a me #se sono qui vuol dire che ci sono giocatori con carte in mano oltre a me
player.rissa_targets = [] player.rissa_targets = []

View File

@ -90,9 +90,9 @@ class ChuckWengam(Character):
if player.lives > 1 and player.is_my_turn: if player.lives > 1 and player.is_my_turn:
import bang.expansions.gold_rush.shop_cards as grc import bang.expansions.gold_rush.shop_cards as grc
player.lives -= 1 player.lives -= 1
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in player.gold_rush_equipment)):
player.gold_nuggets += 1 player.gold_nuggets += 1
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in player.gold_rush_equipment)):
player.hand.append(player.game.deck.draw(True)) player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True)) player.hand.append(player.game.deck.draw(True))
player.hand.append(player.game.deck.draw(True)) player.hand.append(player.game.deck.draw(True))

View File

@ -0,0 +1,231 @@
from typing import List
import bang.roles as r
import bang.players as pl
from bang.cards import Card, Suit, Bang, Mancato
import bang.expansions.fistful_of_cards.card_events as ce
class Fantasma(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Fantasma', number, is_equipment=True)
self.icon = '👻️' #porta in vita i giocatori morti ma non
def play_card(self, player, against, _with=None):
if (player.game.check_event(ce.IlGiudice)):
return False
if len(player.game.get_dead_players(include_ghosts=False)) > 0:
player.pending_action = pl.PendingAction.CHOOSE
player.choose_text = 'choose_fantasma'
player.available_cards = [{
'name': p.name,
'icon': p.role.icon if(player.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'avatar': p.avatar,
'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)),
'is_character': True,
'noDesc': True
} for p in player.game.get_dead_players(include_ghosts=False)]
player.game.deck.scrap(self, True)
return True
return False
class Lemat(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Lemat', number, is_equipment=True, is_weapon=True, range=1)
self.icon = '🔫' # ogni carta può essere usata come bang
#TODO
class SerpenteASonagli(Card):
def __init__(self, suit, number):
super().__init__(suit, 'SerpenteASonagli', number, is_equipment=True)
self.need_target = True
self.icon = '🐍️' # Ogni turno pesca se il seme picche -1hp
self.alt_text = "♠️ =💔"
def play_card(self, player, against, _with=None):
if (player.game.check_event(ce.IlGiudice)):
return False
if against != None:
self.reset_card()
player.sio.emit('chat_message', room=player.game.name,
data=f'_play_card_against|{player.name}|{self.name}|{against}')
player.game.get_player_named(against).equipment.append(self)
player.game.get_player_named(against).notify_self()
return True
return False
class Shotgun(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Shotgun', number, is_equipment=True, is_weapon=True, range=1)
self.icon = '🔫' # Ogni volta che colpisci un giocatore deve scartare una carta
class Taglia(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Taglia', number, is_equipment=True)
self.need_target = True
self.icon = '💰' # chiunque colpisca il giocatore con la taglia pesca una carta dal mazzo, si toglie solo con panico, cat balou, dalton
def play_card(self, player, against, _with=None):
if (player.game.check_event(ce.IlGiudice)):
return False
if against != None:
self.reset_card()
player.sio.emit('chat_message', room=player.game.name,
data=f'_play_card_against|{player.name}|{self.name}|{against}')
player.game.get_player_named(against).equipment.append(self)
player.game.get_player_named(against).notify_self()
return True
return False
class UltimoGiro(Card):
def __init__(self, suit, number):
super().__init__(suit, 'UltimoGiro', number)
self.icon = '🥂'
# self.desc = 'Recupera 1 vita'
# self.desc_eng = 'Regain 1 HP'
self.alt_text = "🍺"
def play_card(self, player, against, _with=None):
super().play_card(player, against)
player.lives = min(player.lives+1, player.max_lives)
player.notify_self()
return True
class Tomahawk(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Tomahawk', number, range=2)
self.icon = '🪓️'
self.alt_text = "2🔎 💥"
# "Spara a un giocatore a distanza 2"
self.need_target = True
def play_card(self, player, against, _with=None):
if against != None and player.game.can_card_reach(self, player, against):
super().play_card(player, against=against)
player.game.attack(player, against, card_name=self.name)
return True
return False
class Tornado(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Tornado', number)
self.icon = '🌪️'
def play_card(self, player, against, _with=None):
player.game.discard_others(player, card_name=self.name)
return True
class Sventagliata(Bang):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = 'Sventagliata'
self.icon = '🎏'
self.alt_text = "💥💥" # spara al target e anche, a uno a distanza 1 dal target
self.need_target = True
def play_card(self, player, against, _with=None):
if against != None:
#TODO
# super().play_card(player, against=against)
# player.game.attack(player, against, card_name=self.name)
return True
return False
class Salvo(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Salvo', number)
self.icon = '😇️'
self.alt_text = "👤😇️"
self.need_target = True
def play_card(self, player, against, _with=None):
if against != None:
#TODO
# super().play_card(player, against=against)
# player.game.attack(player, against, card_name=self.name)
return True
return False
class Mira(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Mira', number)
self.icon = '👌🏻'
self.alt_text = "💥🃏💔💔"
self.need_target = True
self.need_with = True
def play_card(self, player, against, _with=None):
if against != None:
#TODO
# super().play_card(player, against=against)
# player.game.attack(player, against, card_name=self.name)
return True
return False
class Bandidos(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Bandidos', number)
self.icon = '🤠️'
self.alt_text = "👤🃏🃏/💔"
def play_card(self, player, against, _with=None):
player.game.discard_others(player, card_name=self.name)
return True
class Fuga(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Fuga', number)
self.icon = '🏃🏻'
self.alt_text = ""
def play_card(self, player, against, _with=None):
#TODO
# super().play_card(player, against=against)
# player.game.attack(player, against)
return True
class Poker(Card):
def __init__(self, suit, number):
super().__init__(suit, 'Poker', number)
self.icon = '🃏'
self.alt_text = "👤🃏 🃏🃏"
def play_card(self, player, against, _with=None):
#TODO
# super().play_card(player, against=against)
# player.game.attack(player, against)
return True
class RitornoDiFiamma(Mancato):
def __init__(self, suit, number):
super().__init__(suit, number)
self.name = 'RitornoDiFiamma'
self.icon = '🔥'
self.alt_text = "😅 | 💥"
def play_card(self, player, against, _with=None):
return False
def use_card(self, player):
player.notify_self()
def get_starting_deck() -> List[Card]:
cards = [
Fantasma(Suit.SPADES, 9),
Fantasma(Suit.SPADES, 10),
# Lemat(Suit.DIAMONDS, 4),
SerpenteASonagli(Suit.HEARTS, 7),
Shotgun(Suit.SPADES, 'K'),
Taglia(Suit.CLUBS, 9),
UltimoGiro(Suit.DIAMONDS, 8),
Tomahawk(Suit.DIAMONDS, 'A'),
# Sventagliata(Suit.SPADES, 2),
# Salvo(Suit.HEARTS, 5),
Bandidos(Suit.DIAMONDS,'Q'), # gli altri giocatori scelgono se scartare 2 carte o perdere 1 punto vita
# Fuga(Suit.HEARTS, 3), # evita l'effetto di carte marroni (tipo panico cat balou) di cui sei bersaglio
# Mira(Suit.CLUBS, 6),
# Poker(Suit.HEARTS, 'J'), # tutti gli altri scartano 1 carta a scelta, se non ci sono assi allora pesca 2 dal mazzo
RitornoDiFiamma(Suit.CLUBS, 'Q'), # un mancato che fa bang
Tornado(Suit.CLUBS, "A"),
]
for c in cards:
c.expansion_icon = '👻️'
return cards

View File

@ -0,0 +1,10 @@
from typing import List
from bang.characters import Character
def all_characters() -> List[Character]:
cards = [
]
for c in cards:
c.expansion_icon = '👻️'
return cards

View File

@ -5,6 +5,7 @@ import socketio
import eventlet import eventlet
import bang.players as pl import bang.players as pl
import bang.cards as cs
import bang.characters as characters import bang.characters as characters
import bang.expansions.dodge_city.characters as chd import bang.expansions.dodge_city.characters as chd
from bang.deck import Deck from bang.deck import Deck
@ -15,6 +16,34 @@ import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch import bang.expansions.gold_rush.characters as grch
from metrics import Metrics from metrics import Metrics
debug_commands = [
{'cmd':'/debug', 'help':'Toggles the debug mode'},
{'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
{'cmd':'/suicide', 'help':'Kills you'},
{'cmd':'/nextevent', 'help':'Flip the next event card'},
{'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
{'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
{'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
{'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
# {'cmd':'/togglebot', 'help':''},
{'cmd':'/cancelgame', 'help':'Stops the current game'},
{'cmd':'/startgame', 'help':'Force starts the game'},
{'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
# {'cmd':'/addex', 'help':''},
{'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
{'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
{'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
{'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
{'cmd':'/meinfo', 'help':'Get player data'},
{'cmd':'/gameinfo', 'help':'Get game data'},
{'cmd':'/playerinfo', 'help':'Get player data - sample /playerinfo player'},
{'cmd':'/cardinfo', 'help':'Get card data - sample /cardinfo handindex'},
{'cmd':'/mebot', 'help':'Toggles bot mode'},
{'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
{'cmd':'/startwithseed', 'help':'start the game with custom seed'},
{'cmd':'/getset', 'help':'get extension set of cards sample - /get valley', 'admin':True},
]
class Game: class Game:
def __init__(self, name, sio:socketio): def __init__(self, name, sio:socketio):
super().__init__() super().__init__()
@ -52,6 +81,11 @@ class Game:
self.rpc_log = [] self.rpc_log = []
self.is_replay = False self.is_replay = False
def shuffle_players(self):
if not self.started:
random.shuffle(self.players)
self.notify_room()
def reset(self): def reset(self):
for p in self.players: for p in self.players:
if (p.sid == p.name): if (p.sid == p.name):
@ -137,7 +171,7 @@ class Game:
# chat_message(None, cmd[2], player) # chat_message(None, cmd[2], player)
if i == fast_forward: if i == fast_forward:
self.replay_speed = 1.0 self.replay_speed = 1.0
self.notify_room()
eventlet.sleep(max(self.replay_speed, 0.1)) eventlet.sleep(max(self.replay_speed, 0.1))
eventlet.sleep(6) eventlet.sleep(6)
if self.is_replay: if self.is_replay:
@ -145,11 +179,11 @@ class Game:
def notify_room(self, sid=None): def notify_room(self, sid=None):
if len([p for p in self.players if p.character == None]) != 0 or sid: if any((p.character == None for p in self.players)) or sid:
self.sio.emit('room', room=self.name if not sid else sid, data={ self.sio.emit('room', room=self.name if not sid else sid, data={
'name': self.name, 'name': self.name,
'started': self.started, 'started': self.started,
'players': [{'name':p.name, 'ready': p.character != None, 'is_bot': p.is_bot} for p in self.players], 'players': [{'name':p.name, 'ready': p.character != None, 'is_bot': p.is_bot, 'avatar': p.avatar} for p in self.players],
'password': self.password, 'password': self.password,
'is_competitive': self.is_competitive, 'is_competitive': self.is_competitive,
'disconnect_bot': self.disconnect_bot, 'disconnect_bot': self.disconnect_bot,
@ -158,30 +192,7 @@ class Game:
}) })
self.sio.emit('debug', room=self.name, data=self.debug) self.sio.emit('debug', room=self.name, data=self.debug)
if self.debug: if self.debug:
commands = [ self.sio.emit('commands', room=self.name, data=[x for x in debug_commands if 'admin' not in x])
{'cmd':'/debug', 'help':'Toggles the debug mode'},
{'cmd':'/set_chars', 'help':'Set how many characters to distribute - sample /set_chars 3'},
{'cmd':'/suicide', 'help':'Kills you'},
{'cmd':'/nextevent', 'help':'Flip the next event card'},
{'cmd':'/notify', 'help':'Send a message to a player - sample /notify player hi!'},
{'cmd':'/show_cards', 'help':'View the hand of another - sample /show_cards player'},
{'cmd':'/ddc', 'help':'Destroy all cards - sample /ddc player'},
{'cmd':'/dsh', 'help':'Set health - sample /dsh player'},
# {'cmd':'/togglebot', 'help':''},
{'cmd':'/cancelgame', 'help':'Stops the current game'},
{'cmd':'/startgame', 'help':'Force starts the game'},
{'cmd':'/setbotspeed', 'help':'Changes the bot response time - sample /setbotspeed 0.5'},
# {'cmd':'/addex', 'help':''},
{'cmd':'/setcharacter', 'help':'Changes your current character - sample /setcharacter Willy The Kid'},
{'cmd':'/setevent', 'help':'Changes the event deck - sample /setevent 0 Manette'},
{'cmd':'/removecard', 'help':'Remove a card from hand/equip - sample /removecard 0'},
{'cmd':'/getcard', 'help':'Get a brand new card - sample /getcard Birra'},
{'cmd':'/meinfo', 'help':'Get player data'},
{'cmd':'/gameinfo', 'help':'Get game data'},
{'cmd':'/mebot', 'help':'Toggles bot mode'},
{'cmd':'/getnuggets', 'help':'Adds nuggets to yourself - sample /getnuggets 5'},
{'cmd':'/startwithseed', 'help':'start the game with custom seed'}]
self.sio.emit('commands', room=self.name, data=commands)
else: else:
self.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}]) self.sio.emit('commands', room=self.name, data=[{'cmd':'/debug', 'help':'Toggles the debug mode'}])
self.sio.emit('spectators', room=self.name, data=len(self.spectators)) self.sio.emit('spectators', room=self.name, data=len(self.spectators))
@ -203,6 +214,11 @@ class Game:
self.disconnect_bot = not self.disconnect_bot self.disconnect_bot = not self.disconnect_bot
self.notify_room() self.notify_room()
def feature_flags(self):
if 'the_valley_of_shadows' not in self.expansions:
self.available_expansions.append('the_valley_of_shadows')
self.notify_room()
def add_player(self, player: pl.Player): def add_player(self, player: pl.Player):
if player.is_bot and len(self.players) >= 8: if player.is_bot and len(self.players) >= 8:
return return
@ -212,6 +228,8 @@ class Game:
if 'dodge_city' not in self.expansions: if 'dodge_city' not in self.expansions:
self.expansions.append('dodge_city') self.expansions.append('dodge_city')
player.join_game(self) player.join_game(self)
if player.is_admin():
self.feature_flags()
self.players.append(player) self.players.append(player)
print(f'{self.name}: Added player {player.name} to game') print(f'{self.name}: Added player {player.name} to game')
self.notify_room() self.notify_room()
@ -231,7 +249,7 @@ class Game:
def notify_character_selection(self): def notify_character_selection(self):
self.notify_room() self.notify_room()
if len([p for p in self.players if p.character == None]) == 0: if not any((p.character == None for p in self.players)):
for i in range(len(self.players)): for i in range(len(self.players)):
print(self.name, self.players[i].name, self.players[i].character) print(self.name, self.players[i].name, self.players[i].character)
self.sio.emit('chat_message', room=self.name, data=f'_choose_character|{self.players[i].name}|{self.players[i].character.name}') self.sio.emit('chat_message', room=self.name, data=f'_choose_character|{self.players[i].name}|{self.players[i].character.name}')
@ -303,6 +321,22 @@ class Game:
self.players[i].notify_self() self.players[i].notify_self()
self.notify_event_card() self.notify_event_card()
def discard_others(self, attacker: pl.Player, card_name:str=None):
self.attack_in_progress = True
attacker.pending_action = pl.PendingAction.WAIT
attacker.notify_self()
self.waiting_for = 0
self.ready_count = 0
for p in self.get_alive_players():
if len(p.hand) > 0 and (p != attacker or card_name == 'Tornado'):
if p.get_discarded(attacker=attacker, card_name=card_name):
self.waiting_for += 1
p.notify_self()
if self.waiting_for == 0:
attacker.pending_action = pl.PendingAction.PLAY
attacker.notify_self()
self.attack_in_progress = False
def attack_others(self, attacker: pl.Player, card_name:str=None): def attack_others(self, attacker: pl.Player, card_name:str=None):
self.attack_in_progress = True self.attack_in_progress = True
attacker.pending_action = pl.PendingAction.WAIT attacker.pending_action = pl.PendingAction.WAIT
@ -339,6 +373,11 @@ class Game:
if self.pending_winners and not self.someone_won: if self.pending_winners and not self.someone_won:
return self.announces_winners() return self.announces_winners()
def can_card_reach(self, card: cs.Card, player: pl.Player, target:str):
if card and card.range != 0 and card.range < 99:
return not any((True for p in self.get_visible_players(player) if p['name'] == target and p['dist'] > card.range))
return True
def attack(self, attacker: pl.Player, target_username:str, double:bool=False, card_name:str=None): def attack(self, attacker: pl.Player, target_username:str, double:bool=False, card_name:str=None):
if self.get_player_named(target_username).get_banged(attacker=attacker, double=double, card_name=card_name): if self.get_player_named(target_username).get_banged(attacker=attacker, double=double, card_name=card_name):
self.ready_count = 0 self.ready_count = 0
@ -426,11 +465,11 @@ class Game:
print(f'{self.name}: stop roulette') print(f'{self.name}: stop roulette')
target_pl.lives -= 1 target_pl.lives -= 1
target_pl.heal_if_needed() target_pl.heal_if_needed()
if len([c for c in target_pl.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in target_pl.gold_rush_equipment)):
target_pl.gold_nuggets += 1 target_pl.gold_nuggets += 1
if target_pl.character.check(self, grch.SimeonPicos): if target_pl.character.check(self, grch.SimeonPicos):
target_pl.gold_nuggets += 1 target_pl.gold_nuggets += 1
if len([c for c in target_pl.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in target_pl.gold_rush_equipment)):
target_pl.hand.append(self.deck.draw(True)) target_pl.hand.append(self.deck.draw(True))
target_pl.notify_self() target_pl.notify_self()
self.is_russian_roulette_on = False self.is_russian_roulette_on = False
@ -503,14 +542,14 @@ class Game:
pl.hand.append(self.deck.draw()) pl.hand.append(self.deck.draw())
pl.hand.append(self.deck.draw()) pl.hand.append(self.deck.draw())
pl.notify_self() pl.notify_self()
elif self.check_event(ceh.CittaFantasma): elif self.check_event(ceh.CittaFantasma) or self.players[self.turn].is_ghost:
print(f'{self.name}: {self.players[self.turn]} is dead, event ghost') print(f'{self.name}: {self.players[self.turn]} is dead, event ghost')
self.players[self.turn].is_ghost = True self.players[self.turn].is_ghost = True
else: else:
print(f'{self.name}: {self.players[self.turn]} is dead, next turn') print(f'{self.name}: {self.players[self.turn]} is dead, next turn')
return self.next_turn() return self.next_turn()
self.player_bangs = 0 self.player_bangs = 0
if isinstance(self.players[self.turn].role, roles.Sheriff) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any([p for p in self.players if p.is_dead and p.role.name == 'Vice']) and isinstance(self.players[self.turn].role, roles.Renegade))): if isinstance(self.players[self.turn].role, roles.Sheriff) or ((self.initial_players == 3 and isinstance(self.players[self.turn].role, roles.Vice) and not self.players[self.turn].is_ghost) or (self.initial_players == 3 and any((p for p in self.players if p.is_dead and p.role.name == 'Vice')) and isinstance(self.players[self.turn].role, roles.Renegade))):
self.deck.flip_event() self.deck.flip_event()
if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None: if len(self.deck.event_cards) > 0 and self.deck.event_cards[0] != None:
print(f'{self.name}: flip new event {self.deck.event_cards[0].name}') print(f'{self.name}: flip new event {self.deck.event_cards[0].name}')
@ -600,9 +639,9 @@ class Game:
player.game = None player.game = None
if self.disconnect_bot and self.started: if self.disconnect_bot and self.started:
player.is_bot = True player.is_bot = True
if len([p for p in self.players if not p.is_bot]) == 0: if not any((not p.is_bot for p in self.players)):
eventlet.sleep(5) eventlet.sleep(5)
if len([p for p in self.players if not p.is_bot]) == 0: if not any((not p.is_bot for p in self.players)):
print(f'{self.name}: no players left in game, shutting down') print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True self.shutting_down = True
self.players = [] self.players = []
@ -620,7 +659,7 @@ class Game:
# else: # else:
# player.lives = 0 # player.lives = 0
# self.players.remove(player) # self.players.remove(player)
if len([p for p in self.players if not p.is_bot]) == 0: if not any((not p.is_bot for p in self.players)):
print(f'{self.name}: no players left in game, shutting down') print(f'{self.name}: no players left in game, shutting down')
self.shutting_down = True self.shutting_down = True
self.players = [] self.players = []
@ -637,7 +676,7 @@ class Game:
if player.character and player.role: if player.character and player.role:
if not self.is_replay: if not self.is_replay:
Metrics.send_metric('player_death', points=[1], tags=[f"char:{player.character.name}", f"role:{player.role.name}"]) Metrics.send_metric('player_death', points=[1], tags=[f"char:{player.character.name}", f"role:{player.role.name}"])
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0 and player.attacker and player.attacker in self.players: if any((isinstance(c, grc.Ricercato) for c in player.gold_rush_equipment)) and player.attacker and player.attacker in self.players:
player.attacker.gold_nuggets += 1 player.attacker.gold_nuggets += 1
player.attacker.hand.append(self.deck.draw(True)) player.attacker.hand.append(self.deck.draw(True))
player.attacker.hand.append(self.deck.draw(True)) player.attacker.hand.append(self.deck.draw(True))
@ -763,14 +802,15 @@ class Game:
'is_ghost': pls[j].is_ghost, 'is_ghost': pls[j].is_ghost,
'is_bot': pls[j].is_bot, 'is_bot': pls[j].is_bot,
'icon': pls[j].role.icon if (pls[j].role is not None) else '🤠', 'icon': pls[j].role.icon if (pls[j].role is not None) else '🤠',
'avatar': pls[j].avatar,
'role': pls[j].role, 'role': pls[j].role,
} for j in range(len(pls)) if i != j] } for j in range(len(pls)) if i != j]
def get_alive_players(self): def get_alive_players(self):
return [p for p in self.players if not p.is_dead or p.is_ghost] return [p for p in self.players if not p.is_dead or p.is_ghost]
def get_dead_players(self): def get_dead_players(self, include_ghosts=True):
return [p for p in self.players if p.is_dead] return [p for p in self.players if p.is_dead and (include_ghosts or not p.is_ghost)]
def notify_all(self): def notify_all(self):
if self.started: if self.started:
@ -788,6 +828,7 @@ class Game:
'character': p.character.__dict__ if p.character else None, 'character': p.character.__dict__ if p.character else None,
'real_character': p.real_character.__dict__ if p.real_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 '🤠', 'icon': p.role.icon if self.initial_players == 3 and p.role else '🤠',
'avatar': p.avatar,
'is_ghost': p.is_ghost, 'is_ghost': p.is_ghost,
'is_bot': p.is_bot, 'is_bot': p.is_bot,
} for p in self.get_alive_players()] } for p in self.get_alive_players()]

View File

@ -12,10 +12,23 @@ import bang.expansions.fistful_of_cards.card_events as ce
import bang.expansions.high_noon.card_events as ceh import bang.expansions.high_noon.card_events as ceh
import bang.expansions.gold_rush.shop_cards as grc import bang.expansions.gold_rush.shop_cards as grc
import bang.expansions.gold_rush.characters as grch import bang.expansions.gold_rush.characters as grch
import bang.expansions.the_valley_of_shadows.cards as tvosc
import eventlet import eventlet
from typing import List from typing import List
from metrics import Metrics from metrics import Metrics
robot_pictures = [
'https://i.imgur.com/40rAFIb.jpg',
'https://i.imgur.com/gG77VRR.jpg',
'https://i.imgur.com/l2DTQeH.jpg',
'https://i.imgur.com/aPM2gix.jpg',
'https://i.imgur.com/ep5EB8c.jpg',
'https://i.imgur.com/qsOWIsf.jpg',
'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/apple/325/robot_1f916.png',
'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/openmoji/338/robot_1f916.png',
'https://emojipedia-us.s3.dualstack.us-west-1.amazonaws.com/thumbs/240/microsoft/319/robot_1f916.png',
]
class PendingAction(IntEnum): class PendingAction(IntEnum):
PICK = 0 PICK = 0
DRAW = 1 DRAW = 1
@ -26,13 +39,46 @@ class PendingAction(IntEnum):
class Player: class Player:
def __init__(self, name, sid, sio, bot=False): def is_admin(self):
return self.discord_id in {'244893980960096266'}
def get_avatar(self):
import requests
headers = {
'Authorization': 'Bearer ' + self.discord_token,
}
r = requests.get('https://discordapp.com/api/users/@me', headers=headers)
if r.status_code == 200:
res = r.json()
self.avatar = f'https://cdn.discordapp.com/avatars/{res["id"]}/{res["avatar"]}.png'
if self.game:
self.sio.emit('chat_message', room=self.game.name, data=f'_change_username|{self.name}|{res["username"]}')
self.name = res['username']
self.discord_id = res['id']
if self.is_admin():
if self.game: self.game.feature_flags()
self.sio.emit('chat_message', room=self.sid, data={'color':'green', 'text':'(you are admin)'})
if self.game:
self.game.notify_room()
self.sio.emit('me', data=self.name, room=self.sid)
else:
print('error getting avatar', r.status_code, r.text)
print(r)
def __init__(self, name, sid, sio, bot=False, discord_token=None):
import bang.game as g import bang.game as g
super().__init__() super().__init__()
self.name = name self.name = name
self.sid = sid self.sid = sid
self.sio = sio self.sio = sio
self.is_bot = bot self.is_bot = bot
self.discord_token = discord_token
self.discord_id = None
self.avatar = ''
if self.is_bot:
self.avatar = robot_pictures[randrange(len(robot_pictures))]
if self.discord_token:
sio.start_background_task(self.get_avatar)
self.game: g = None self.game: g = None
self.reset() self.reset()
@ -163,6 +209,10 @@ class Player:
self.sio.emit('notify_card', room=self.sid, data=mess) self.sio.emit('notify_card', room=self.sid, data=mess)
def notify_self(self): def notify_self(self):
if any((True for c in self.equipment if isinstance(c, tvosc.Fantasma))):
self.is_ghost = True
elif self.is_ghost and not self.game.check_event(ceh.CittaFantasma):
self.is_ghost = False
if self.is_ghost: self.lives = 0 if self.is_ghost: self.lives = 0
if self.pending_action == PendingAction.DRAW and self.game.check_event(ce.Peyote): if self.pending_action == PendingAction.DRAW and self.game.check_event(ce.Peyote):
self.available_cards = [{ self.available_cards = [{
@ -187,7 +237,7 @@ class Player:
self.hand.append(self.game.deck.draw(True)) self.hand.append(self.game.deck.draw(True))
if self.lives <= 0 and self.max_lives > 0 and not self.is_dead: if self.lives <= 0 and self.max_lives > 0 and not self.is_dead:
print('dying, attacker', self.attacker) print('dying, attacker', self.attacker)
if self.gold_nuggets >= 2 and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Zaino)]) > 0: if self.gold_nuggets >= 2 and any((isinstance(c, grc.Zaino) for c in self.gold_rush_equipment)):
for i in range(len(self.gold_rush_equipment)): for i in range(len(self.gold_rush_equipment)):
if isinstance(self.gold_rush_equipment[i], grc.Zaino): if isinstance(self.gold_rush_equipment[i], grc.Zaino):
self.gold_rush_equipment[i].play_card(self, None) self.gold_rush_equipment[i].play_card(self, None)
@ -202,11 +252,13 @@ class Player:
self.choose_text = 'choose_sid_scrap' self.choose_text = 'choose_sid_scrap'
self.available_cards = self.hand self.available_cards = self.hand
self.lives += 1 self.lives += 1
ser = self.__dict__.copy() ser = self.__dict__.copy()
ser.pop('game') ser.pop('game')
ser.pop('sio') ser.pop('sio')
ser.pop('sid') ser.pop('sid')
ser.pop('on_pick_cb') ser.pop('on_pick_cb')
ser.pop('discord_token')
ser.pop('on_failed_response_cb') ser.pop('on_failed_response_cb')
ser.pop('attacker') ser.pop('attacker')
ser.pop('rissa_targets') ser.pop('rissa_targets')
@ -214,7 +266,7 @@ class Player:
ser['attacker'] = self.attacker.name ser['attacker'] = self.attacker.name
ser['sight'] = self.get_sight() ser['sight'] = self.get_sight()
ser['sight_extra'] = self.get_sight(False) -1 ser['sight_extra'] = self.get_sight(False) -1
ser['can_gold_rush_discard'] = len([p for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number <= self.gold_nuggets + 1]) > 0]) > 0 ser['can_gold_rush_discard'] = any((p != self and any((e.number <= self.gold_nuggets + 1 for e in p.gold_rush_equipment)) for p in self.game.get_alive_players()))
if self.character: if self.character:
ser['gold_rush_discount'] = 1 if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1 else 0 ser['gold_rush_discount'] = 1 if self.character.check(self.game, grch.PrettyLuzena) and self.special_use_count < 1 else 0
ser['lives'] = max(ser['lives'], 0) ser['lives'] = max(ser['lives'], 0)
@ -223,14 +275,12 @@ class Player:
self.pending_action = PendingAction.WAIT self.pending_action = PendingAction.WAIT
ser['hand'] = [] ser['hand'] = []
ser['equipment'] = [] ser['equipment'] = []
self.sio.emit('self', room=self.sid, data=json.dumps( self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__))
ser, default=lambda o: o.__dict__))
self.game.player_death(self) self.game.player_death(self)
if self.game and self.game.started: # falso quando un bot viene eliminato dalla partita if self.game and self.game.started: # falso quando un bot viene eliminato dalla partita
self.sio.emit('self_vis', room=self.sid, data=json.dumps(self.game.get_visible_players(self), default=lambda o: o.__dict__)) self.sio.emit('self_vis', room=self.sid, data=json.dumps(self.game.get_visible_players(self), default=lambda o: o.__dict__))
self.game.notify_all() self.game.notify_all()
self.sio.emit('self', room=self.sid, data=json.dumps( self.sio.emit('self', room=self.sid, data=json.dumps(ser, default=lambda o: o.__dict__))
ser, default=lambda o: o.__dict__))
def bot_spin(self): def bot_spin(self):
while self.is_bot and self.game != None and not self.game.shutting_down: while self.is_bot and self.game != None and not self.game.shutting_down:
@ -253,10 +303,10 @@ class Player:
self.draw('') self.draw('')
elif self.pending_action == PendingAction.PLAY: elif self.pending_action == PendingAction.PLAY:
non_blocked_cards = [card for card in self.hand if (not self.game.check_event(ceh.Manette) or card.suit == self.committed_suit_manette)] non_blocked_cards = [card for card in self.hand if (not self.game.check_event(ceh.Manette) or card.suit == self.committed_suit_manette)]
equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not any([type(c) == type(x) and not (c.is_weapon and c.must_be_used) for x in self.equipment])] equippables = [c for c in non_blocked_cards if (c.is_equipment or c.usable_next_turn) and not isinstance(c, cs.Prigione) and not c.need_target and not any((type(c) == type(x) and not (c.is_weapon and c.must_be_used) for x in self.equipment))]
misc = [c for c in non_blocked_cards if not c.need_target and (isinstance(c, cs.WellsFargo) or isinstance(c, cs.Indiani) or isinstance(c, cs.Gatling) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or ((isinstance(c, cs.Birra) and self.lives < self.max_lives or c.must_be_used) and not self.game.check_event(ceh.IlReverendo)) or (c.need_with and len(self.hand) > 1 and not (isinstance(c, csd.Whisky) and self.lives == self.max_lives))) misc = [c for c in non_blocked_cards if not c.need_target and (isinstance(c, cs.WellsFargo) or isinstance(c, cs.Indiani) or isinstance(c, cs.Gatling) or isinstance(c, cs.Diligenza) or isinstance(c, cs.Emporio) or ((isinstance(c, cs.Birra) and self.lives < self.max_lives or c.must_be_used) and not self.game.check_event(ceh.IlReverendo)) or (c.need_with and len(self.hand) > 1 and not (isinstance(c, csd.Whisky) and self.lives == self.max_lives)))
and not (not c.can_be_used_now and self.game.check_event(ce.IlGiudice)) and not c.is_equipment] and not (not c.can_be_used_now and self.game.check_event(ce.IlGiudice)) and not c.is_equipment]
need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (type(c) == type(cs.Bang) and (self.game.check_event(ceh.Sermone) or (self.has_played_bang and (not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or self.game.check_event(ce.Lazo))))) and not (isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice)) or isinstance(c, cs.Duello) or isinstance(c, cs.CatBalou) or isinstance(c, csd.Pugno)] need_target = [c for c in non_blocked_cards if c.need_target and c.can_be_used_now and not (c.need_with and len(self.hand) < 2) and not (type(c) == type(cs.Bang) and (self.game.check_event(ceh.Sermone) or (self.has_played_bang and (not any((isinstance(c, cs.Volcanic) for c in self.equipment)) or self.game.check_event(ce.Lazo))))) and not (isinstance(c, cs.Prigione) and self.game.check_event(ce.IlGiudice)) or isinstance(c, cs.Duello) or isinstance(c, cs.CatBalou) or isinstance(c, csd.Pugno)]
green_cards = [c for c in self.equipment if not self.game.check_event(ce.Lazo) and not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now] green_cards = [c for c in self.equipment if not self.game.check_event(ce.Lazo) and not isinstance(c, cs.Mancato) and c.usable_next_turn and c.can_be_used_now]
if self.game.debug: if self.game.debug:
print(f'hand: {self.hand}') print(f'hand: {self.hand}')
@ -265,7 +315,7 @@ class Player:
print(f'misc: {misc}') print(f'misc: {misc}')
print(f'need_target: {need_target}') print(f'need_target: {need_target}')
print(f'green_cards: {green_cards}') print(f'green_cards: {green_cards}')
if self.gold_nuggets > 0 and any([c.number <= self.gold_nuggets for c in self.game.deck.shop_cards]): if self.gold_nuggets > 0 and any((c.number <= self.gold_nuggets for c in self.game.deck.shop_cards)):
for i in range(len(self.game.deck.shop_cards)): for i in range(len(self.game.deck.shop_cards)):
if self.game.deck.shop_cards[i].number <= self.gold_nuggets: if self.game.deck.shop_cards[i].number <= self.gold_nuggets:
self.game.rpc_log.append(f'{self.name};buy_gold_rush_card;{i}') self.game.rpc_log.append(f'{self.name};buy_gold_rush_card;{i}')
@ -273,6 +323,8 @@ class Player:
return return
if len(equippables) > 0 and not self.game.check_event(ce.IlGiudice): if len(equippables) > 0 and not self.game.check_event(ce.IlGiudice):
for c in equippables: for c in equippables:
if isinstance(c, tvosc.Fantasma) and len(self.game.get_dead_players(include_ghosts=False)) == 0:
continue
if self.play_card(self.hand.index(c)): if self.play_card(self.hand.index(c)):
return return
elif len(misc) > 0: elif len(misc) > 0:
@ -318,7 +370,7 @@ class Player:
return return
break break
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10 maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0: if maxcards == self.lives and any((isinstance(c, grc.Cinturone) for c in self.gold_rush_equipment)):
maxcards = 8 maxcards = 8
if len(self.hand) > maxcards: if len(self.hand) > maxcards:
self.game.rpc_log.append(f'{self.name};scrap;{0}') self.game.rpc_log.append(f'{self.name};scrap;{0}')
@ -376,19 +428,19 @@ class Player:
data=f'_turn|{self.name}') data=f'_turn|{self.name}')
print(f'{self.name}: I was notified that it is my turn') print(f'{self.name}: I was notified that it is my turn')
self.was_shot = False self.was_shot = False
self.attacker = None
self.is_my_turn = True self.is_my_turn = True
self.is_waiting_for_action = True self.is_waiting_for_action = True
self.has_played_bang = False self.has_played_bang = False
self.special_use_count = 0 self.special_use_count = 0
self.bang_used = 0 self.bang_used = 0
if self.game.check_event(ceh.MezzogiornoDiFuoco): if self.game.check_event(ceh.MezzogiornoDiFuoco):
self.attacker = None
self.lives -= 1 self.lives -= 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1 self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos): if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1 self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw(True)) self.hand.append(self.game.deck.draw(True))
if self.character.check(self.game, chars.BartCassidy) and self.lives > 0: if self.character.check(self.game, chars.BartCassidy) and self.lives > 0:
self.hand.append(self.game.deck.draw(True)) self.hand.append(self.game.deck.draw(True))
@ -398,11 +450,12 @@ class Player:
return self.notify_self() return self.notify_self()
#non è un elif perchè vera custer deve fare questo poi cambiare personaggio #non è un elif perchè vera custer deve fare questo poi cambiare personaggio
if self.game.check_event(ce.FratelliDiSangue) and self.lives > 1 and not self.is_giving_life and len([p for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives]): if self.game.check_event(ce.FratelliDiSangue) and self.lives > 1 and not self.is_giving_life and sum(p != self and p.lives < p.max_lives for p in self.game.get_alive_players()):
self.available_cards = [{ self.available_cards = [{
'name': p.name, 'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠', 'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)), 'alt_text': ''.join(['❤️']*p.lives)+''.join(['💀']*(p.max_lives-p.lives)),
'avatar': p.avatar,
'is_character': True, 'is_character': True,
'noDesc': True 'noDesc': True
} for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives] } for p in self.game.get_alive_players() if p != self and p.lives < p.max_lives]
@ -414,7 +467,7 @@ class Player:
self.available_cards = [self.character, self.not_chosen_character] self.available_cards = [self.character, self.not_chosen_character]
self.choose_text = 'choose_nuova_identita' self.choose_text = 'choose_nuova_identita'
self.pending_action = PendingAction.CHOOSE self.pending_action = PendingAction.CHOOSE
elif not self.game.check_event(ce.Lazo) and any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): elif not self.game.check_event(ce.Lazo) and any((isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) or isinstance(c, tvosc.SerpenteASonagli) for c in self.equipment)):
self.is_giving_life = False self.is_giving_life = False
self.pending_action = PendingAction.PICK self.pending_action = PendingAction.PICK
else: else:
@ -426,7 +479,7 @@ class Player:
self.notify_self() self.notify_self()
def draw(self, pile): def draw(self, pile):
if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Cecchino) and len([c for c in self.hand if c.name == cs.Bang(0,0).name]) >= 2: if self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Cecchino) and sum((c.name == cs.Bang(0,0).name for c in self.hand)) >= 2:
self.is_using_checchino = True self.is_using_checchino = True
self.available_cards = [{ self.available_cards = [{
'name': p['name'], 'name': p['name'],
@ -439,11 +492,12 @@ class Player:
self.choose_text = 'choose_cecchino' self.choose_text = 'choose_cecchino'
self.pending_action = PendingAction.CHOOSE self.pending_action = PendingAction.CHOOSE
self.notify_self() self.notify_self()
elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Rimbalzo) and len([c for c in self.hand if c.name == cs.Bang(0,0).name]) > 0: elif self.is_my_turn and self.pending_action == PendingAction.PLAY and pile == 'event' and self.game.check_event(ce.Rimbalzo) and any((c.name == cs.Bang(0,0).name for c in self.hand)):
self.available_cards = [{ self.available_cards = [{
'name': p.name, 'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠', 'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'is_character': True, 'is_character': True,
'avatar': p.avatar,
'noDesc': True 'noDesc': True
} for p in self.game.get_alive_players() if len(p.equipment) > 0 and p != self] } for p in self.game.get_alive_players() if len(p.equipment) > 0 and p != self]
self.available_cards.append({'icon': '', 'noDesc': True}) self.available_cards.append({'icon': '', 'noDesc': True})
@ -480,7 +534,7 @@ class Player:
self.pending_action = PendingAction.PLAY self.pending_action = PendingAction.PLAY
num = 2 if not self.character.check(self.game, chd.BillNoface) else self.max_lives-self.lives+1 num = 2 if not self.character.check(self.game, chd.BillNoface) else self.max_lives-self.lives+1
if self.character.check(self.game, chd.PixiePete): num += 1 if self.character.check(self.game, chd.PixiePete): num += 1
if (len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0): num += 1 if (any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment))): num += 1
if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.check_event(ceh.CittaFantasma)): num += 1 if self.game.check_event(ceh.IlTreno) or (self.is_ghost and self.game.check_event(ceh.CittaFantasma)): num += 1
elif self.game.check_event(ceh.Sete): num -= 1 elif self.game.check_event(ceh.Sete): num -= 1
for i in range(num): for i in range(num):
@ -522,9 +576,9 @@ class Player:
if self.pending_action != PendingAction.PICK: if self.pending_action != PendingAction.PICK:
return return
pickable_cards = 1 + self.character.pick_mod pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0: if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1 pickable_cards += 1
if self.is_my_turn: if self.is_my_turn and self.attacker == None:
for i in range(len(self.equipment)): for i in range(len(self.equipment)):
if i < len(self.equipment) and isinstance(self.equipment[i], cs.Dinamite): if i < len(self.equipment) and isinstance(self.equipment[i], cs.Dinamite):
while pickable_cards > 0: while pickable_cards > 0:
@ -535,11 +589,11 @@ class Player:
data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}') data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if picked.check_suit(self.game, [cs.Suit.SPADES]) and 2 <= picked.number <= 9 and pickable_cards == 0: if picked.check_suit(self.game, [cs.Suit.SPADES]) and 2 <= picked.number <= 9 and pickable_cards == 0:
self.lives -= 3 self.lives -= 3
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 3 self.gold_nuggets += 3
if self.character.check(self.game, grch.SimeonPicos): if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 3 self.gold_nuggets += 3
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
@ -557,7 +611,7 @@ class Player:
self.game.next_player().equipment.append(self.equipment.pop(i)) self.game.next_player().equipment.append(self.equipment.pop(i))
self.game.next_player().notify_self() self.game.next_player().notify_self()
break break
if any([isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) for c in self.equipment]): if any((isinstance(c, cs.Dinamite) or isinstance(c, cs.Prigione) or isinstance(c, tvosc.SerpenteASonagli) for c in self.equipment)):
self.notify_self() self.notify_self()
return return
for i in range(len(self.equipment)): for i in range(len(self.equipment)):
@ -578,7 +632,21 @@ class Player:
self.sio.emit('chat_message', room=self.game.name, data=f'_prison_free|{self.name}') self.sio.emit('chat_message', room=self.game.name, data=f'_prison_free|{self.name}')
break break
break break
if any([isinstance(c, cs.Prigione) for c in self.equipment]): for i in range(len(self.equipment)):
if isinstance(self.equipment[i], tvosc.SerpenteASonagli):
while pickable_cards > 0:
pickable_cards -= 1
picked: cs.Card = self.game.deck.pick_and_scrap()
print(f'Did pick {picked}')
self.sio.emit('chat_message', room=self.game.name,
data=f'_flipped|{self.name}|{picked.name}|{picked.num_suit()}')
if not picked.check_suit(self.game, [cs.Suit.SPADES]):
break
elif pickable_cards == 0:
self.lives -= 1
self.sio.emit('chat_message', room=self.game.name, data=f'_snake_bit|{self.name}')
break
if any((isinstance(c, cs.Prigione) for c in self.equipment)):
self.notify_self() self.notify_self()
return return
if isinstance(self.real_character, chd.VeraCuster): if isinstance(self.real_character, chd.VeraCuster):
@ -594,7 +662,7 @@ class Player:
playable_cards = [] playable_cards = []
for i in range(len(self.hand)): for i in range(len(self.hand)):
card = self.hand[i] card = self.hand[i]
if isinstance(card, cs.Bang) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]): if isinstance(card, cs.Bang) and self.has_played_bang and not any((isinstance(c, cs.Volcanic) for c in self.equipment)):
continue continue
elif isinstance(card, cs.Birra) and self.lives >= self.max_lives: elif isinstance(card, cs.Birra) and self.lives >= self.max_lives:
continue continue
@ -627,9 +695,9 @@ class Player:
withCard = self.hand.pop(_with) if hand_index > _with else self.hand.pop(_with - 1) 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) print(self.name, 'is playing ', card, ' against:', against, ' with:', _with)
did_play_card = False did_play_card = False
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) or (self.game.check_event(ceh.Manette) and card.suit != self.committed_suit_manette and not (card.usable_next_turn and card.can_be_used_now)) 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) or ((self.game.check_event(ceh.Manette) and card.suit != self.committed_suit_manette) and not (card.usable_next_turn and card.can_be_used_now))
if not(against != None and (self.game.get_player_named(against).character.check(self.game, chd.ApacheKid) or len([c for c in self.game.get_player_named(against).gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) and not event_blocks_card: if not(against != None and (self.game.get_player_named(against).character.check(self.game, chd.ApacheKid) or any((isinstance(c, grc.Calumet) for c in self.game.get_player_named(against).gold_rush_equipment))) and card.check_suit(self.game, [cs.Suit.DIAMONDS])) or (isinstance(card, grc.ShopCard) and card.kind == grc.ShopCardKind.BLACK) and not event_blocks_card:
if against == self.name and not isinstance(card, csd.Tequila) and not isinstance(card, cs.Panico) and not isinstance(card, cs.CatBalou): if (against == self.name and not isinstance(card, csd.Tequila) and not isinstance(card, cs.Panico) and not isinstance(card, cs.CatBalou)) or event_blocks_card:
did_play_card = False did_play_card = False
else: else:
did_play_card = card.play_card(self, against, withCard) did_play_card = card.play_card(self, against, withCard)
@ -665,7 +733,7 @@ class Player:
target = self.game.get_player_named(self.target_p) target = self.game.get_player_named(self.target_p)
card = None card = None
if (target.name == self.name): if (target.name == self.name):
card = self.equipment.pop(card_index) card = self.equipment.pop(card_index if card_index < len(target.hand) else card_index - len(target.hand))
elif card_index >= len(target.hand): elif card_index >= len(target.hand):
card = target.equipment.pop(card_index - len(target.hand)) card = target.equipment.pop(card_index - len(target.hand))
else: else:
@ -688,7 +756,7 @@ class Player:
elif self.choose_text == 'choose_ricercato': elif self.choose_text == 'choose_ricercato':
player = self.game.get_player_named(self.available_cards[card_index]['name']) player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{self.name}|Ricercato|{player.name}') player.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{self.name}|Ricercato|{player.name}')
if len([c for c in player.gold_rush_equipment if isinstance(c, grc.Ricercato)]) > 0: if any((isinstance(c, grc.Ricercato) for c in player.gold_rush_equipment)):
self.game.deck.shop_deck.append(grc.Ricercato()) self.game.deck.shop_deck.append(grc.Ricercato())
else: else:
player.gold_rush_equipment.append(grc.Ricercato()) player.gold_rush_equipment.append(grc.Ricercato())
@ -753,6 +821,34 @@ class Player:
player.notify_self() player.notify_self()
self.pending_action = PendingAction.PLAY self.pending_action = PendingAction.PLAY
self.notify_self() self.notify_self()
elif 'choose_fantasma' in self.choose_text:
if card_index <= len(self.available_cards):
player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.equipment.append(self.game.deck.scrap_pile.pop(-1))
player.notify_self()
self.game.notify_all()
self.sio.emit('chat_message', room=player.game.name, data=f'_play_card_against|{player.name}|Fantasma|{player.name}')
self.pending_action = PendingAction.PLAY
self.notify_self()
elif 'choose_tornado' in self.choose_text:
if card_index <= len(self.available_cards):
self.game.deck.scrap_pile.append(self.hand.pop(card_index))
self.hand.append(self.game.deck.draw())
self.hand.append(self.game.deck.draw())
self.pending_action = PendingAction.WAIT
self.game.responders_did_respond_resume_turn()
self.notify_self()
elif 'choose_bandidos' in self.choose_text:
if card_index <= len(self.available_cards):
self.available_cards.pop(card_index)
self.game.deck.scrap_pile.append(self.hand.pop(card_index))
self.mancato_needed -= 1
else:
self.lives -= 1
if self.mancato_needed <= 0:
self.pending_action = PendingAction.WAIT
self.game.responders_did_respond_resume_turn()
self.notify_self()
elif self.game.check_event(ceh.NuovaIdentita) and self.choose_text == 'choose_nuova_identita': elif self.game.check_event(ceh.NuovaIdentita) and self.choose_text == 'choose_nuova_identita':
if card_index == 1: # the other character if card_index == 1: # the other character
self.character = self.not_chosen_character self.character = self.not_chosen_character
@ -771,11 +867,11 @@ class Player:
player = self.game.get_player_named(self.available_cards[card_index]['name']) player = self.game.get_player_named(self.available_cards[card_index]['name'])
player.lives += 1 player.lives += 1
self.lives -= 1 self.lives -= 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1 self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos): if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1 self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
player.notify_self() player.notify_self()
self.sio.emit('chat_message', room=self.game.name, data=f'_fratelli_sangue|{self.name}|{player.name}') self.sio.emit('chat_message', room=self.game.name, data=f'_fratelli_sangue|{self.name}|{player.name}')
@ -855,7 +951,7 @@ class Player:
self.hand.append(card) self.hand.append(card)
pickable_stop = 1 pickable_stop = 1
if self.game.check_event(ceh.Sete): pickable_stop += 1 if self.game.check_event(ceh.Sete): pickable_stop += 1
if self.game.check_event(ceh.IlTreno) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0: if self.game.check_event(ceh.IlTreno) or any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
pickable_stop -= 1 pickable_stop -= 1
if len(self.available_cards) == pickable_stop: if len(self.available_cards) == pickable_stop:
if len(self.available_cards) > 0: #la carta non scelta la rimettiamo in cima al mazzo if len(self.available_cards) > 0: #la carta non scelta la rimettiamo in cima al mazzo
@ -863,7 +959,7 @@ class Player:
if len(self.available_cards) > 0: #se sono rimaste carte le scartiamo if len(self.available_cards) > 0: #se sono rimaste carte le scartiamo
self.game.deck.scrap(self.available_cards.pop()) self.game.deck.scrap(self.available_cards.pop())
#se c'è sia treno che piccone pesco un'altra carta #se c'è sia treno che piccone pesco un'altra carta
if self.game.check_event(ceh.IlTreno) and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0: if self.game.check_event(ceh.IlTreno) and any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
self.is_drawing = False self.is_drawing = False
self.pending_action = PendingAction.PLAY self.pending_action = PendingAction.PLAY
@ -879,7 +975,7 @@ class Player:
#legge del west non si applica perchè la seconda carta viene scartata #legge del west non si applica perchè la seconda carta viene scartata
if self.game.check_event(ceh.IlTreno): if self.game.check_event(ceh.IlTreno):
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Piccone)]) > 0: if any((isinstance(c, grc.Piccone) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw()) self.hand.append(self.game.deck.draw())
self.gold_nuggets += 1 self.gold_nuggets += 1
self.is_drawing = False self.is_drawing = False
@ -903,9 +999,9 @@ class Player:
def barrel_pick(self): def barrel_pick(self):
pickable_cards = 1 + self.character.pick_mod pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0: if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1 pickable_cards += 1
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais): if any((isinstance(c, cs.Barile) for c in self.equipment)) and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2 pickable_cards = 2
while pickable_cards > 0: while pickable_cards > 0:
pickable_cards -= 1 pickable_cards -= 1
@ -919,8 +1015,8 @@ class Player:
if self.mancato_needed <= 0: if self.mancato_needed <= 0:
self.game.responders_did_respond_resume_turn(did_lose=False) self.game.responders_did_respond_resume_turn(did_lose=False)
return return
if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\ if not self.game.is_competitive and not any((isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
self.take_damage_response() self.take_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True) self.game.responders_did_respond_resume_turn(did_lose=True)
else: else:
@ -933,9 +1029,9 @@ class Player:
def barrel_pick_no_dmg(self): def barrel_pick_no_dmg(self):
pickable_cards = 1 + self.character.pick_mod pickable_cards = 1 + self.character.pick_mod
if len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0 and self.character.check(self.game, chars.Jourdonnais): if any((isinstance(c, cs.Barile) for c in self.equipment)) and self.character.check(self.game, chars.Jourdonnais):
pickable_cards = 2 pickable_cards = 2
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.FerroDiCavallo)]) > 0: if any((isinstance(c, grc.FerroDiCavallo) for c in self.gold_rush_equipment)):
pickable_cards += 1 pickable_cards += 1
while pickable_cards > 0: while pickable_cards > 0:
pickable_cards -= 1 pickable_cards -= 1
@ -949,8 +1045,8 @@ class Player:
if self.mancato_needed <= 0: if self.mancato_needed <= 0:
self.game.responders_did_respond_resume_turn(did_lose=False) self.game.responders_did_respond_resume_turn(did_lose=False)
return return
if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\ if not self.game.is_competitive and not any((isinstance(c, cs.Mancato) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
self.take_no_damage_response() self.take_no_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True) self.game.responders_did_respond_resume_turn(did_lose=True)
else: else:
@ -961,6 +1057,17 @@ class Player:
self.on_failed_response_cb = self.take_no_damage_response self.on_failed_response_cb = self.take_no_damage_response
self.notify_self() self.notify_self()
def get_discarded(self, attacker=None, card_name=None):
self.pending_action = PendingAction.CHOOSE
self.available_cards = self.hand.copy()
if card_name == 'Tornado':
self.choose_text = 'choose_tornado'
if card_name == 'Bandidos':
self.choose_text = 'choose_bandidos'
self.mancato_needed = min(2, len(self.hand))
self.available_cards.append({'name': '-1hp', 'icon': '💔', 'noDesc': True})
return True
def get_banged(self, attacker, double=False, no_dmg=False, card_index=None, card_name=None): def get_banged(self, attacker, double=False, no_dmg=False, card_index=None, card_name=None):
self.attacker = attacker self.attacker = attacker
self.attacking_card = card_name self.attacking_card = card_name
@ -973,9 +1080,9 @@ class Player:
for i in range(len(self.equipment)): for i in range(len(self.equipment)):
if self.equipment[i].can_be_used_now: if self.equipment[i].can_be_used_now:
print('usable', self.equipment[i]) print('usable', self.equipment[i])
if not self.game.is_competitive and len([c for c in self.equipment if isinstance(c, cs.Barile)]) == 0 and not self.character.check(self.game, chars.Jourdonnais)\ if not self.game.is_competitive and not any((isinstance(c, cs.Barile) for c in self.equipment)) and not self.character.check(self.game, chars.Jourdonnais)\
and len([c for c in self.hand if (isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente)]) == 0\ and not any(((isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand))\
and len([c for c in self.equipment if c.can_be_used_now and isinstance(c, cs.Mancato)]) == 0: and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
print('Cant defend') print('Cant defend')
if not no_dmg: if not no_dmg:
self.take_damage_response() self.take_damage_response()
@ -983,7 +1090,7 @@ class Player:
self.take_no_damage_response() self.take_no_damage_response()
return False return False
else: else:
if ((not self.game.check_event(ce.Lazo) and len([c for c in self.equipment if isinstance(c, cs.Barile)]) > 0) \ if ((not self.game.check_event(ce.Lazo) and any((isinstance(c, cs.Barile) for c in self.equipment))) \
and not (self.game.players[self.game.turn].character.check(self.game, chd.BelleStar) and isinstance(attacker, Player))) \ and not (self.game.players[self.game.turn].character.check(self.game, chd.BelleStar) and isinstance(attacker, Player))) \
or self.character.check(self.game, chars.Jourdonnais): #se ho un barile e non c'è lazo e non mi sta attaccando Belle Star o se sono Jourdonnais or self.character.check(self.game, chars.Jourdonnais): #se ho un barile e non c'è lazo e non mi sta attaccando Belle Star o se sono Jourdonnais
print('has barrel') print('has barrel')
@ -1019,8 +1126,8 @@ class Player:
def get_indians(self, attacker): def get_indians(self, attacker):
self.attacker = attacker self.attacker = attacker
self.attacking_card = "Indiani!" self.attacking_card = "Indiani!"
if self.character.check(self.game, chd.ApacheKid) or len([c for c in self.gold_rush_equipment if isinstance(c, grc.Calumet)]) > 0: return False if self.character.check(self.game, chd.ApacheKid) or any((isinstance(c, grc.Calumet) for c in self.gold_rush_equipment)): return False
if not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0: if not self.game.is_competitive and not any((isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato)) for c in self.hand)):
print('Cant defend') print('Cant defend')
self.take_damage_response() self.take_damage_response()
return False return False
@ -1037,7 +1144,7 @@ class Player:
def get_dueled(self, attacker): def get_dueled(self, attacker):
self.attacker = attacker self.attacker = attacker
self.attacking_card = "Duello" self.attacking_card = "Duello"
if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (not self.game.is_competitive and len([c for c in self.hand if isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato))]) == 0): if (self.game.check_event(ceh.Sermone) and self.is_my_turn) or (not self.game.is_competitive and not any((isinstance(c, cs.Bang) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Mancato)) for c in self.hand))):
print('Cant defend') print('Cant defend')
self.take_damage_response() self.take_damage_response()
self.game.responders_did_respond_resume_turn(did_lose=True) self.game.responders_did_respond_resume_turn(did_lose=True)
@ -1052,7 +1159,7 @@ class Player:
return True return True
def heal_if_needed(self): def heal_if_needed(self):
while self.lives <= 0 and len(self.game.get_alive_players()) > 2 and len([c for c in self.hand if isinstance(c, cs.Birra)]) > 0 and not self.game.check_event(ceh.IlReverendo): while self.lives <= 0 and len(self.game.get_alive_players()) > 2 and any((isinstance(c, cs.Birra) for c in self.hand)) and not self.game.check_event(ceh.IlReverendo):
for i in range(len(self.hand)): for i in range(len(self.hand)):
if isinstance(self.hand[i], cs.Birra): if isinstance(self.hand[i], cs.Birra):
if self.character.check(self.game, chd.MollyStark) and not self.is_my_turn: if self.character.check(self.game, chd.MollyStark) and not self.is_my_turn:
@ -1066,6 +1173,7 @@ class Player:
def take_damage_response(self): def take_damage_response(self):
self.lives -= 1 self.lives -= 1
self.sio.emit('hurt', room=self.sid, data=f'')
if self.lives > 0: if self.lives > 0:
if self.character.check(self.game, chars.BartCassidy): if self.character.check(self.game, chars.BartCassidy):
self.sio.emit('chat_message', room=self.game.name, self.sio.emit('chat_message', room=self.game.name,
@ -1077,15 +1185,24 @@ class Player:
self.sio.emit('chat_message', room=self.game.name, self.sio.emit('chat_message', room=self.game.name,
data=f'_special_el_gringo|{self.name}|{self.attacker.name}') data=f'_special_el_gringo|{self.name}|{self.attacker.name}')
self.attacker.notify_self() self.attacker.notify_self()
if self.attacker and any((isinstance(c, tvosc.Taglia) for c in self.equipment)):
self.attacker.hand.append(self.game.deck.draw(True))
self.sio.emit('chat_message', room=self.game.name,
data=f'_taglia_reward|{self.name}|{self.attacker.name}')
self.attacker.notify_self()
if self.attacker and len(self.hand) > 0 and any((isinstance(c, tvosc.Shotgun) for c in self.attacker.equipment)):
c = self.hand.pop(randrange(0, len(self.hand)))
self.game.deck.scrap(c, True)
self.sio.emit('chat_message', room=self.game.name, data=f'_shotgun_scrap|{self.name}|{c.name}')
if self.attacker and 'gold_rush' in self.game.expansions: if self.attacker and 'gold_rush' in self.game.expansions:
if (isinstance(self.attacker, Player)): if (isinstance(self.attacker, Player)):
self.attacker.gold_nuggets += 1 self.attacker.gold_nuggets += 1
self.attacker.notify_self() self.attacker.notify_self()
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Talismano)]) > 0: if any((isinstance(c, grc.Talismano) for c in self.gold_rush_equipment)):
self.gold_nuggets += 1 self.gold_nuggets += 1
if self.character.check(self.game, grch.SimeonPicos): if self.character.check(self.game, grch.SimeonPicos):
self.gold_nuggets += 1 self.gold_nuggets += 1
if len([c for c in self.gold_rush_equipment if isinstance(c, grc.Stivali)]) > 0: if any((isinstance(c, grc.Stivali) for c in self.gold_rush_equipment)):
self.hand.append(self.game.deck.draw(True)) self.hand.append(self.game.deck.draw(True))
self.heal_if_needed() self.heal_if_needed()
self.mancato_needed = 0 self.mancato_needed = 0
@ -1137,7 +1254,15 @@ class Player:
self.molly_discarded_cards = 0 self.molly_discarded_cards = 0
self.notify_self() self.notify_self()
self.game.responders_did_respond_resume_turn(did_lose=False) self.game.responders_did_respond_resume_turn(did_lose=False)
if isinstance(card, tvosc.RitornoDiFiamma):
self.game.attack(self, self.attacker.name, card_name=card.name)
self.event_type = '' self.event_type = ''
elif not any(((isinstance(c, cs.Mancato) and c.can_be_used_now) or (self.character.check(self.game, chars.CalamityJanet) and isinstance(c, cs.Bang)) or self.character.check(self.game, chd.ElenaFuente) for c in self.hand)) and not any((c.can_be_used_now and isinstance(c, cs.Mancato) for c in self.equipment)):
self.on_failed_response_cb()
if self.game:
self.game.responders_did_respond_resume_turn(did_lose=True)
if isinstance(card, tvosc.RitornoDiFiamma) and self.attacker:
self.game.attack(self, self.attacker.name, card_name=card.name)
else: else:
self.pending_action = PendingAction.RESPOND self.pending_action = PendingAction.RESPOND
self.notify_self() self.notify_self()
@ -1205,9 +1330,10 @@ class Player:
'name': p.name, 'name': p.name,
'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠', 'icon': p.role.icon if(self.game.initial_players == 3) else '⭐️' if isinstance(p.role, r.Sheriff) else '🤠',
'is_character': True, 'is_character': True,
'avatar': p.avatar,
'alt_text': ''.join(['🎴️'] * len(p.gold_rush_equipment)), 'alt_text': ''.join(['🎴️'] * len(p.gold_rush_equipment)),
'noDesc': True 'noDesc': True
} for p in self.game.get_alive_players() if p != self and len([e for e in p.gold_rush_equipment if e.number + 1 <= self.gold_nuggets]) > 0] } for p in self.game.get_alive_players() if p != self and any((e.number + 1 <= self.gold_nuggets for e in p.gold_rush_equipment))]
self.available_cards.append({'icon': '', 'noDesc': True}) self.available_cards.append({'icon': '', 'noDesc': True})
self.choose_text = 'gold_rush_discard' self.choose_text = 'gold_rush_discard'
self.pending_action = PendingAction.CHOOSE self.pending_action = PendingAction.CHOOSE
@ -1234,23 +1360,23 @@ class Player:
if self.game.check_event(ce.LeggeDelWest) and len(must_be_used_cards) > 0: if self.game.check_event(ce.LeggeDelWest) and len(must_be_used_cards) > 0:
card = must_be_used_cards[0] card = must_be_used_cards[0]
print(f'Legge del west card: {card.name}') print(f'Legge del west card: {card.name}')
print(self.has_played_bang and not (any([isinstance(c, cs.Volcanic) for c in self.equipment]) and type(card) == type(cs.Bang))) print(self.has_played_bang and not (any((isinstance(c, cs.Volcanic) for c in self.equipment)) and type(card) == type(cs.Bang)))
if card.suit == cs.Suit.DIAMONDS and card.need_target and len([p for p in self.game.get_alive_players() if (not p.character.check(self.game, chd.ApacheKid) and not any([isinstance(c, grc.Calumet) for c in p.gold_rush_equipment]))]) == 0: if card.suit == cs.Suit.DIAMONDS and card.need_target and not any(((not p.character.check(self.game, chd.ApacheKid) and not any((isinstance(c, grc.Calumet) for c in p.gold_rush_equipment))) for p in self.game.get_alive_players())):
if isinstance(card, cs.Bang): if isinstance(card, cs.Bang):
return True return True
else: else:
return len(self.equipment) == 0 # se non ho carte equipaggiamento return len(self.equipment) == 0 # se non ho carte equipaggiamento
elif (isinstance(card, cs.Bang) or (isinstance(card, cs.Mancato) and self.character.check(self.game, chars.CalamityJanet))) and self.has_played_bang and not any([isinstance(c, cs.Volcanic) for c in self.equipment]) or len([p for p in self.game.get_visible_players(self) if self.get_sight() >= p['dist']]) == 0: elif (isinstance(card, cs.Bang) or (isinstance(card, cs.Mancato) and self.character.check(self.game, chars.CalamityJanet))) and self.has_played_bang and not any((isinstance(c, cs.Volcanic) for c in self.equipment)) or not any((self.get_sight() >= p['dist'] for p in self.game.get_visible_players(self))):
return True return True
elif isinstance(card, cs.Mancato) or (card.need_with and len(self.hand) < 2): elif isinstance(card, cs.Mancato) or (card.need_with and len(self.hand) < 2):
return True return True
elif isinstance(card, cs.Panico) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0 and len(self.equipment) == 0: elif isinstance(card, cs.Panico) and not any((self.get_sight(False) >= p['dist'] for p in self.game.get_visible_players(self))) and len(self.equipment) == 0:
return True return True
elif isinstance(card, csd.Pugno) and len([p for p in self.game.get_visible_players(self) if self.get_sight(False) >= p['dist']]) == 0: elif isinstance(card, csd.Pugno) and not any((self.get_sight(False) >= p['dist'] for p in self.game.get_visible_players(self))):
return True return True
elif isinstance(card, cs.Prigione) and len([p for p in self.game.get_visible_players(self) if not p['is_sheriff']]) == 0: elif isinstance(card, cs.Prigione) and not any((not p['is_sheriff'] for p in self.game.get_visible_players(self))):
return True return True
elif not card.is_weapon and len([c for c in self.equipment if c.name == card.name]) > 0: elif not card.is_weapon and any((c.name == card.name for c in self.equipment)):
return True return True
return False return False
return True return True
@ -1260,7 +1386,7 @@ class Player:
if not self.is_my_turn and not forced: if not self.is_my_turn and not forced:
return return
maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10 maxcards = self.lives if not self.character.check(self.game, chd.SeanMallory) else 10
if maxcards == self.lives and len([c for c in self.gold_rush_equipment if isinstance(c, grc.Cinturone)]) > 0: if maxcards == self.lives and any((isinstance(c, grc.Cinturone) for c in self.gold_rush_equipment)):
maxcards = 8 maxcards = 8
if len(self.hand) > maxcards and not forced: if len(self.hand) > maxcards and not forced:
print(f"{self.name}: I have to many cards in my hand and I can't end the turn") print(f"{self.name}: I have to many cards in my hand and I can't end the turn")
@ -1290,7 +1416,7 @@ class Player:
self.play_turn(can_play_vendetta=False) self.play_turn(can_play_vendetta=False)
return return
##Ghost## ##Ghost##
if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma): if self.is_dead and self.is_ghost and self.game.check_event(ceh.CittaFantasma) and not any((True for c in self.equipment if isinstance(c, tvosc.Fantasma))):
self.is_ghost = False self.is_ghost = False
for i in range(len(self.hand)): for i in range(len(self.hand)):
self.game.deck.scrap(self.hand.pop(), True) self.game.deck.scrap(self.hand.pop(), True)

View File

@ -21,7 +21,7 @@ class Sheriff(Role):
def on_player_death(self, alive_players: list, initial_players: int, dead_role=None, attacker_role=None): def on_player_death(self, alive_players: list, initial_players: int, dead_role=None, attacker_role=None):
if initial_players == 3 and len(alive_players) == 1: if initial_players == 3 and len(alive_players) == 1:
return True return True
elif initial_players != 3 and not any([isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players]): elif initial_players != 3 and not any((isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players)):
print("The Sheriff won!") print("The Sheriff won!")
return True return True
return False return False
@ -42,7 +42,7 @@ class Vice(Role):
return True return True
elif initial_players == 3 and attacker_role != None: elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Renegade) and isinstance(attacker_role, Vice) return isinstance(dead_role, Renegade) and isinstance(attacker_role, Vice)
elif initial_players != 3 and not any([isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players]): elif initial_players != 3 and not any((isinstance(p.role, Outlaw) or isinstance(p.role, Renegade) for p in alive_players)):
print("The Vice won!") print("The Vice won!")
return True return True
return False return False
@ -62,9 +62,9 @@ class Outlaw(Role):
return True return True
elif initial_players == 3 and attacker_role != None: elif initial_players == 3 and attacker_role != None:
return isinstance(dead_role, Vice) and isinstance(attacker_role, Outlaw) return isinstance(dead_role, Vice) and isinstance(attacker_role, Outlaw)
elif (initial_players != 3 and (not any([isinstance(p.role, Sheriff) for p in alive_players])) elif (initial_players != 3 and (not any((isinstance(p.role, Sheriff) for p in alive_players)))
and (any([isinstance(p.role, Outlaw) for p in alive_players]) and (any((isinstance(p.role, Outlaw) for p in alive_players))
or any([isinstance(p.role, Renegade) for p in alive_players]) and len(alive_players) > 1)): or any((isinstance(p.role, Renegade) for p in alive_players)) and len(alive_players) > 1)):
print("The Outlaw won!") print("The Outlaw won!")
return True return True
return False return False

View File

@ -14,11 +14,16 @@ from discord_webhook import DiscordWebhook
from metrics import Metrics from metrics import Metrics
from ddtrace import tracer from ddtrace import tracer
Metrics.init() import sys
import traceback
import sys
sys.setrecursionlimit(10**6) # this should prevents bots from stopping sys.setrecursionlimit(10**6) # this should prevents bots from stopping
import logging
logging.basicConfig(filename='out.log', level='ERROR')
from functools import wraps
Metrics.init()
sio = socketio.Server(cors_allowed_origins="*") sio = socketio.Server(cors_allowed_origins="*")
static_files={ static_files={
@ -45,13 +50,30 @@ games: List[Game] = []
online_players = 0 online_players = 0
blacklist: List[str] = [] blacklist: List[str] = []
def send_to_debug(error):
for g in games:
if g.debug or any((p.is_admin() for p in g.players)):
sio.emit('chat_message', room=g.name, data={'color': f'red','text':json.dumps({'ERROR':error}), 'type':'json'})
def bang_handler(func):
@wraps(func)
def wrapper_func(*args, **kwargs):
try:
func(*args, **kwargs)
except Exception as e:
logging.exception(e)
print(traceback.format_exc())
send_to_debug(traceback.format_exc())
return wrapper_func
def advertise_lobbies(): def advertise_lobbies():
sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if not g.started and len(g.players) < 10 and not g.is_hidden]) sio.emit('lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if not g.started and len(g.players) < 10 and not g.is_hidden])
sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started]) sio.emit('spectate_lobbies', room='lobby', data=[{'name': g.name, 'players': len(g.players), 'locked': g.password != ''} for g in games if g.started and not g.is_hidden])
Metrics.send_metric('lobbies', points=[len([g for g in games if not g.is_replay])]) Metrics.send_metric('lobbies', points=[sum(not g.is_replay for g in games)])
Metrics.send_metric('online_players', points=[online_players]) Metrics.send_metric('online_players', points=[online_players])
@sio.event @sio.event
@bang_handler
def connect(sid, environ): def connect(sid, environ):
global online_players global online_players
online_players += 1 online_players += 1
@ -61,40 +83,46 @@ def connect(sid, environ):
Metrics.send_metric('online_players', points=[online_players]) Metrics.send_metric('online_players', points=[online_players])
@sio.event @sio.event
@bang_handler
def get_online_players(sid): def get_online_players(sid):
global online_players global online_players
sio.emit('players', room='lobby', data=online_players) sio.emit('players', room='lobby', data=online_players)
@sio.event @sio.event
@bang_handler
def report(sid, text): def report(sid, text):
print(f'New report from {sid}: {text}')
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
data='' data=''
if hasattr(ses, 'game'): if hasattr(ses, 'game'):
data = "\n".join(ses.game.rpc_log[:-1]).strip() data = "\n".join(ses.game.rpc_log[:-1]).strip()
data = data +"\n@@@\n" +text data = data +"\n@@@\n" +text
#print(data) #print(data)
response = requests.post("https://www.toptal.com/developers/hastebin/documents", data) response = requests.post("https://hastebin.com/documents", data.encode('utf-8'))
key = json.loads(response.text).get('key') key = json.loads(response.text).get('key')
if "DISCORD_WEBHOOK" in os.environ and len(os.environ['DISCORD_WEBHOOK']) > 0: if "DISCORD_WEBHOOK" in os.environ and len(os.environ['DISCORD_WEBHOOK']) > 0:
webhook = DiscordWebhook(url=os.environ['DISCORD_WEBHOOK'], content=f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}') webhook = DiscordWebhook(url=os.environ['DISCORD_WEBHOOK'], content=f'New bug report, replay at https://bang.xamin.it/game?replay={key}')
response = webhook.execute() response = webhook.execute()
sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Report OK'}) sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Report OK'})
else: else:
print("WARNING: DISCORD_WEBHOOK not found") print("WARNING: DISCORD_WEBHOOK not found")
Metrics.send_event('BUG_REPORT', event_data=text) Metrics.send_event('BUG_REPORT', event_data=text)
print(f'New bug report, replay at https://www.toptal.com/developers/hastebin/{key}') print(f'New bug report, replay at https://bang.xamin.it/game?replay={key}')
@sio.event @sio.event
@bang_handler
def set_username(sid, username): def set_username(sid, username):
ses = sio.get_session(sid) ses = sio.get_session(sid)
if not isinstance(ses, Player): if not isinstance(ses, Player):
sio.save_session(sid, Player(username, sid, sio)) dt = username["discord_token"] if 'discord_token' in username else None
sio.save_session(sid, Player(username["name"], sid, sio, discord_token=dt))
print(f'{sid} is now {username}') print(f'{sid} is now {username}')
advertise_lobbies() advertise_lobbies()
elif ses.game == None or not ses.game.started: elif ses.game == None or not ses.game.started:
username = username["name"]
print(f'{sid} changed username to {username}') print(f'{sid} changed username to {username}')
prev = ses.name prev = ses.name
if len([p for p in ses.game.players if p.name == username]) > 0: if ses.game and any((p.name == username for p in ses.game.players)):
ses.name = f"{username}_{random.randint(0,100)}" ses.name = f"{username}_{random.randint(0,100)}"
else: else:
ses.name = username ses.name = username
@ -104,19 +132,34 @@ def set_username(sid, username):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def get_me(sid, room): def get_me(sid, room):
if isinstance(sio.get_session(sid), Player): if isinstance(sio.get_session(sid), Player):
sio.emit('me', data=sio.get_session(sid).name, room=sid) sio.emit('me', data=sio.get_session(sid).name, room=sid)
if sio.get_session(sid).game: if sio.get_session(sid).game:
sio.get_session(sid).game.notify_room() sio.get_session(sid).game.notify_room()
else: else:
sio.save_session(sid, Player('player', sid, sio)) dt = room["discord_token"] if 'discord_token' in room else None
sio.save_session(sid, Player('player', sid, sio, discord_token=dt))
if 'replay' in room and room['replay'] != None:
create_room(sid, room['replay'])
sid = sio.get_session(sid)
sid.game.is_hidden = True
eventlet.sleep(0.5)
response = requests.get(f"https://hastebin.com/raw/{room['replay']}")
if response.status_code != 200:
sio.emit('chat_message', room=sid, data={'color': f'green','text':f'Invalid replay code'})
return
log = response.text.splitlines()
sid.game.spectators.append(sid)
sid.game.replay(log)
return
de_games = [g for g in games if g.name == room['name']] de_games = [g for g in games if g.name == room['name']]
if len(de_games) == 1 and not de_games[0].started: if len(de_games) == 1 and not de_games[0].started:
join_room(sid, room) join_room(sid, room)
elif len(de_games) == 1 and de_games[0].started: elif len(de_games) == 1 and de_games[0].started:
print('room exists') print('room exists')
if room['username'] != None and any([p.name == room['username'] for p in de_games[0].players if p.is_bot]): if room['username'] != None and any((p.name == room['username'] for p in de_games[0].players if (p.is_bot or (dt != None and p.discord_token == dt)))):
print('getting inside the bot') print('getting inside the bot')
bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0] bot = [p for p in de_games[0].players if p.is_bot and p.name == room['username'] ][0]
bot.sid = sid bot.sid = sid
@ -126,6 +169,7 @@ def get_me(sid, room):
de_games[0].notify_room(sid) de_games[0].notify_room(sid)
eventlet.sleep(0.1) eventlet.sleep(0.1)
de_games[0].notify_all() de_games[0].notify_all()
de_games[0].notify_scrap_pile(sid)
sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__)) sio.emit('role', room=sid, data=json.dumps(bot.role, default=lambda o: o.__dict__))
bot.notify_self() bot.notify_self()
if len(bot.available_characters) > 0: if len(bot.available_characters) > 0:
@ -135,6 +179,8 @@ def get_me(sid, room):
sio.get_session(sid).game = de_games[0] sio.get_session(sid).game = de_games[0]
sio.enter_room(sid, de_games[0].name) sio.enter_room(sid, de_games[0].name)
de_games[0].notify_room(sid) de_games[0].notify_room(sid)
eventlet.sleep(0.1)
de_games[0].notify_event_card(sid) de_games[0].notify_event_card(sid)
de_games[0].notify_scrap_pile(sid) de_games[0].notify_scrap_pile(sid)
de_games[0].notify_all() de_games[0].notify_all()
@ -146,7 +192,7 @@ def get_me(sid, room):
sio.emit('me', data={'error':'Wrong password/Cannot connect'}, room=sid) sio.emit('me', data={'error':'Wrong password/Cannot connect'}, room=sid)
else: else:
sio.emit('me', data=sio.get_session(sid).name, room=sid) sio.emit('me', data=sio.get_session(sid).name, room=sid)
if room['username'] == None or any([p.name == room['username'] for p in sio.get_session(sid).game.players]): if room['username'] == None or any((p.name == room['username'] for p in sio.get_session(sid).game.players)):
sio.emit('change_username', room=sid) sio.emit('change_username', room=sid)
else: else:
sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}") sio.emit('chat_message', room=sio.get_session(sid).game.name, data=f"_change_username|{sio.get_session(sid).name}|{room['username']}")
@ -157,6 +203,7 @@ def get_me(sid, room):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def disconnect(sid): def disconnect(sid):
global online_players global online_players
online_players -= 1 online_players -= 1
@ -164,16 +211,18 @@ def disconnect(sid):
sio.emit('players', room='lobby', data=online_players) sio.emit('players', room='lobby', data=online_players)
if sio.get_session(sid).game and sio.get_session(sid).disconnect(): if sio.get_session(sid).game and sio.get_session(sid).disconnect():
sio.close_room(sio.get_session(sid).game.name) sio.close_room(sio.get_session(sid).game.name)
games.pop(games.index(sio.get_session(sid).game)) if sio.get_session(sid).game in games:
games.pop(games.index(sio.get_session(sid).game))
print('disconnect ', sid) print('disconnect ', sid)
advertise_lobbies() advertise_lobbies()
Metrics.send_metric('online_players', points=[online_players]) Metrics.send_metric('online_players', points=[online_players])
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def create_room(sid, room_name): def create_room(sid, room_name):
if sio.get_session(sid).game == None: if sio.get_session(sid).game == None:
while len([g for g in games if g.name == room_name]): while any((g.name == room_name for g in games)):
room_name += f'_{random.randint(0,100)}' room_name += f'_{random.randint(0,100)}'
sio.leave_room(sid, 'lobby') sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name) sio.enter_room(sid, room_name)
@ -186,26 +235,31 @@ def create_room(sid, room_name):
advertise_lobbies() advertise_lobbies()
@sio.event @sio.event
@bang_handler
def private(sid): def private(sid):
g = sio.get_session(sid).game g = sio.get_session(sid).game
g.set_private() g.set_private()
advertise_lobbies() advertise_lobbies()
@sio.event @sio.event
@bang_handler
def toggle_expansion(sid, expansion_name): def toggle_expansion(sid, expansion_name):
g = sio.get_session(sid).game g = sio.get_session(sid).game
g.toggle_expansion(expansion_name) g.toggle_expansion(expansion_name)
@sio.event @sio.event
@bang_handler
def toggle_comp(sid): def toggle_comp(sid):
sio.get_session(sid).game.toggle_competitive() sio.get_session(sid).game.toggle_competitive()
@sio.event @sio.event
@bang_handler
def toggle_replace_with_bot(sid): def toggle_replace_with_bot(sid):
sio.get_session(sid).game.toggle_disconnect_bot() sio.get_session(sid).game.toggle_disconnect_bot()
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def join_room(sid, room): def join_room(sid, room):
room_name = room['name'] room_name = room['name']
i = [g.name for g in games].index(room_name) i = [g.name for g in games].index(room_name)
@ -215,7 +269,7 @@ def join_room(sid, room):
print(f'{sid} joined a room named {room_name}') print(f'{sid} joined a room named {room_name}')
sio.leave_room(sid, 'lobby') sio.leave_room(sid, 'lobby')
sio.enter_room(sid, room_name) sio.enter_room(sid, room_name)
while len([p for p in games[i].players if p.name == sio.get_session(sid).name]): while any((p.name == sio.get_session(sid).name and not p.is_bot for p in games[i].players)):
sio.get_session(sid).name += f'_{random.randint(0,100)}' sio.get_session(sid).name += f'_{random.randint(0,100)}'
sio.emit('me', data=sio.get_session(sid).name, room=sid) sio.emit('me', data=sio.get_session(sid).name, room=sid)
games[i].add_player(sio.get_session(sid)) games[i].add_player(sio.get_session(sid))
@ -236,8 +290,9 @@ Sockets for the status page
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def get_all_rooms(sid, deploy_key): def get_all_rooms(sid, deploy_key):
if 'DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']: if ('DEPLOY_KEY' in os.environ and deploy_key == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
sio.emit('all_rooms', room=sid, data=[{ sio.emit('all_rooms', room=sid, data=[{
'name': g.name, 'name': g.name,
'hidden': g.is_hidden, 'hidden': g.is_hidden,
@ -252,13 +307,15 @@ def get_all_rooms(sid, deploy_key):
} for g in games]) } for g in games])
@sio.event @sio.event
@bang_handler
def kick(sid, data): def kick(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']: if ('DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
sio.emit('kicked', room=data['sid']) sio.emit('kicked', room=data['sid'])
@sio.event @sio.event
@bang_handler
def hide_toogle(sid, data): def hide_toogle(sid, data):
if 'DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']: if ('DEPLOY_KEY' in os.environ and data['key'] == os.environ['DEPLOY_KEY']) or sio.get_session(sid).is_admin():
game = [g for g in games if g.name==data['room']] game = [g for g in games if g.name==data['room']]
if len(games) > 0: if len(games) > 0:
game[0].is_hidden = not game[0].is_hidden game[0].is_hidden = not game[0].is_hidden
@ -274,6 +331,7 @@ Sockets for the game
""" """
@sio.event @sio.event
@bang_handler
def start_game(sid): def start_game(sid):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.start_game() ses.game.start_game()
@ -281,6 +339,13 @@ def start_game(sid):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def shuffle_players(sid):
ses: Player = sio.get_session(sid)
ses.game.shuffle_players()
@sio.event
@bang_handler
def set_character(sid, name): def set_character(sid, name):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};set_character;{name}') ses.game.rpc_log.append(f'{ses.name};set_character;{name}')
@ -289,12 +354,14 @@ def set_character(sid, name):
ses.set_character(name) ses.set_character(name)
@sio.event @sio.event
@bang_handler
def refresh(sid): def refresh(sid):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.notify_self() ses.notify_self()
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def draw(sid, pile): def draw(sid, pile):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};draw;{pile}') ses.game.rpc_log.append(f'{ses.name};draw;{pile}')
@ -302,6 +369,7 @@ def draw(sid, pile):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def pick(sid): def pick(sid):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};pick') ses.game.rpc_log.append(f'{ses.name};pick')
@ -309,6 +377,7 @@ def pick(sid):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def end_turn(sid): def end_turn(sid):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};end_turn') ses.game.rpc_log.append(f'{ses.name};end_turn')
@ -316,6 +385,7 @@ def end_turn(sid):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def play_card(sid, data): def play_card(sid, data):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};play_card;{json.dumps(data)}') ses.game.rpc_log.append(f'{ses.name};play_card;{json.dumps(data)}')
@ -323,6 +393,7 @@ def play_card(sid, data):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def respond(sid, card_index): def respond(sid, card_index):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};respond;{card_index}') ses.game.rpc_log.append(f'{ses.name};respond;{card_index}')
@ -330,6 +401,7 @@ def respond(sid, card_index):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def choose(sid, card_index): def choose(sid, card_index):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};choose;{card_index}') ses.game.rpc_log.append(f'{ses.name};choose;{card_index}')
@ -337,6 +409,7 @@ def choose(sid, card_index):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def scrap(sid, card_index): def scrap(sid, card_index):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};scrap;{card_index}') ses.game.rpc_log.append(f'{ses.name};scrap;{card_index}')
@ -351,6 +424,7 @@ def special(sid, data):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def gold_rush_discard(sid): def gold_rush_discard(sid):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};gold_rush_discard;') ses.game.rpc_log.append(f'{ses.name};gold_rush_discard;')
@ -358,6 +432,7 @@ def gold_rush_discard(sid):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def buy_gold_rush_card(sid, data:int): def buy_gold_rush_card(sid, data:int):
ses: Player = sio.get_session(sid) ses: Player = sio.get_session(sid)
ses.game.rpc_log.append(f'{ses.name};buy_gold_rush_card;{data}') ses.game.rpc_log.append(f'{ses.name};buy_gold_rush_card;{data}')
@ -365,6 +440,7 @@ def buy_gold_rush_card(sid, data:int):
@tracer.wrap @tracer.wrap
@sio.event @sio.event
@bang_handler
def chat_message(sid, msg, pl=None): def chat_message(sid, msg, pl=None):
ses: Player = sio.get_session(sid) if pl is None else pl ses: Player = sio.get_session(sid) if pl is None else pl
ses.game.rpc_log.append(f'{ses.name};chat_message;{msg}') ses.game.rpc_log.append(f'{ses.name};chat_message;{msg}')
@ -379,7 +455,7 @@ def chat_message(sid, msg, pl=None):
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'}) sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'Only 1 bot at the time'})
else: else:
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True) bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
while any([p for p in ses.game.players if p.name == bot.name]): while any((p for p in ses.game.players if p.name == bot.name)):
bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True) bot = Player(f'AI_{random.randint(0,10)}', 'bot', sio, bot=True)
ses.game.add_player(bot) ses.game.add_player(bot)
bot.bot_spin() bot.bot_spin()
@ -388,7 +464,7 @@ def chat_message(sid, msg, pl=None):
_cmd = msg.split() _cmd = msg.split()
if len(_cmd) >= 2: if len(_cmd) >= 2:
replay_id = _cmd[1] replay_id = _cmd[1]
response = requests.get(f"https://www.toptal.com/developers/hastebin/raw/{replay_id}") response = requests.get(f"https://hastebin.com/raw/{replay_id}")
log = response.text.splitlines() log = response.text.splitlines()
ses.game.spectators.append(ses) ses.game.spectators.append(ses)
if len(_cmd) == 2: if len(_cmd) == 2:
@ -407,7 +483,7 @@ def chat_message(sid, msg, pl=None):
ses.game.start_game(int(msg.split()[1])) ses.game.start_game(int(msg.split()[1]))
return return
elif '/removebot' in msg and not ses.game.started: elif '/removebot' in msg and not ses.game.started:
if any([p.is_bot for p in ses.game.players]): if any((p.is_bot for p in ses.game.players)):
[p for p in ses.game.players if p.is_bot][-1].disconnect() [p for p in ses.game.players if p.is_bot][-1].disconnect()
return return
elif '/togglecomp' in msg and ses.game: elif '/togglecomp' in msg and ses.game:
@ -418,13 +494,13 @@ def chat_message(sid, msg, pl=None):
if len(cmd) == 2 and 'DEPLOY_KEY' in os.environ and cmd[1] == os.environ['DEPLOY_KEY']: # solo chi ha la deploy key può attivare la modalità debug if len(cmd) == 2 and 'DEPLOY_KEY' in os.environ and cmd[1] == os.environ['DEPLOY_KEY']: # solo chi ha la deploy key può attivare la modalità debug
ses.game.debug = not ses.game.debug ses.game.debug = not ses.game.debug
ses.game.notify_room() ses.game.notify_room()
elif ses == ses.game.players[0]: # solo l'owner può attivare la modalità debug elif ses == ses.game.players[0] or ses.is_admin(): # solo l'owner può attivare la modalità debug
ses.game.debug = not ses.game.debug ses.game.debug = not ses.game.debug
ses.game.notify_room() ses.game.notify_room()
if ses.game.debug: if ses.game.debug:
sio.emit('chat_message', room=sid, data={'color': f'red','text':f'debug mode is now active, only the owner of the room can disable it with /debug'}) sio.emit('chat_message', room=sid, data={'color': f'red','text':f'debug mode is now active, only the owner of the room can disable it with /debug'})
return return
if not ses.game.debug: if not ses.game.debug and not ses.is_admin():
sio.emit('chat_message', room=sid, data={'color': f'','text':f'debug mode is not active, only the owner of the room can enable it with /debug'}) sio.emit('chat_message', room=sid, data={'color': f'','text':f'debug mode is not active, only the owner of the room can enable it with /debug'})
elif '/set_chars' in msg and not ses.game.started: elif '/set_chars' in msg and not ses.game.started:
cmd = msg.split() cmd = msg.split()
@ -544,6 +620,27 @@ def chat_message(sid, msg, pl=None):
for cn in card_names: for cn in card_names:
ses.hand.append([c for c in cards if c.name.lower() == cn.lower() or c.name[0:-1].lower() == cn.lower()][0]) ses.hand.append([c for c in cards if c.name.lower() == cn.lower() or c.name[0:-1].lower() == cn.lower()][0])
ses.notify_self() ses.notify_self()
elif '/equipcard' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got a card'})
import bang.cards as cs
cmd = msg.split()
if len(cmd) >= 2:
cards = cs.get_starting_deck(ses.game.expansions)
card_names = ' '.join(cmd[1:]).split(',')
for cn in card_names:
ses.equipment.append([c for c in cards if c.name.lower() == cn.lower() or c.name[0:-1].lower() == cn.lower()][0])
ses.notify_self()
elif '/getset' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got a card'})
cmd = msg.split()
if len(cmd) >= 2:
from bang.expansions import DodgeCity, TheValleyOfShadows
if cmd[1] == 'dodgecity':
ses.hand = DodgeCity.get_cards()
ses.notify_self()
elif 'valley' in cmd[1].lower():
ses.hand = TheValleyOfShadows.get_cards()
ses.notify_self()
elif '/getnuggets' in msg: elif '/getnuggets' in msg:
sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got nuggets'}) sio.emit('chat_message', room=ses.game.name, data={'color': f'red','text':f'🚨 {ses.name} is in debug mode and got nuggets'})
import bang.cards as cs import bang.cards as cs
@ -552,16 +649,26 @@ def chat_message(sid, msg, pl=None):
ses.gold_nuggets += int(cmd[1]) ses.gold_nuggets += int(cmd[1])
ses.notify_self() ses.notify_self()
elif '/gameinfo' in msg: elif '/gameinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {dict(filter(lambda x:x[0] != "rpc_log",ses.game.__dict__.items()))}'}) sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.game.__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
elif '/status' in msg and ses.is_admin():
sio.emit('mount_status', room=sid)
elif '/meinfo' in msg: elif '/meinfo' in msg:
sio.emit('chat_message', room=sid, data={'color': f'','text':f'info: {ses.__dict__}'}) sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
elif '/playerinfo' in msg:
cmd = msg.split()
if len(cmd) == 2:
sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.game.get_player_named(cmd[1]).__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
elif '/cardinfo' in msg:
cmd = msg.split()
if len(cmd) == 2:
sio.emit('chat_message', room=sid, data={'color': f'', 'text':json.dumps(ses.hand[int(cmd[1])].__dict__, default=lambda o: f'<{o.__class__.__name__}() not serializable>'), 'type': 'json'})
elif '/mebot' in msg: elif '/mebot' in msg:
ses.is_bot = not ses.is_bot ses.is_bot = not ses.is_bot
if (ses.is_bot): if (ses.is_bot):
ses.was_player = True ses.was_player = True
ses.bot_spin() ses.bot_spin()
elif '/arcadekick' in msg and ses.game.started: elif '/arcadekick' in msg and ses.game.started:
if len([p for p in ses.game.players if p.pending_action != PendingAction.WAIT]) == 0: if not any((p.pending_action != PendingAction.WAIT for p in ses.game.players)):
sio.emit('chat_message', room=ses.game.name, data={'color': f'','text':f'KICKING THE ARCADE CABINET'}) sio.emit('chat_message', room=ses.game.name, data={'color': f'','text':f'KICKING THE ARCADE CABINET'})
ses.game.next_turn() ses.game.next_turn()
else: else:
@ -582,6 +689,7 @@ Sockets for the help screen
""" """
@sio.event @sio.event
@bang_handler
def get_cards(sid): def get_cards(sid):
import bang.cards as c import bang.cards as c
cards = c.get_starting_deck(['dodge_city']) cards = c.get_starting_deck(['dodge_city'])
@ -594,12 +702,14 @@ def get_cards(sid):
Metrics.send_metric('help_screen_viewed', points=[1]) Metrics.send_metric('help_screen_viewed', points=[1])
@sio.event @sio.event
@bang_handler
def get_characters(sid): def get_characters(sid):
import bang.characters as ch import bang.characters as ch
cards = ch.all_characters(['dodge_city', 'gold_rush']) cards = ch.all_characters(['dodge_city', 'gold_rush'])
sio.emit('characters_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__)) sio.emit('characters_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event @sio.event
@bang_handler
def get_highnooncards(sid): def get_highnooncards(sid):
import bang.expansions.high_noon.card_events as ceh import bang.expansions.high_noon.card_events as ceh
chs = [] chs = []
@ -608,6 +718,7 @@ def get_highnooncards(sid):
sio.emit('highnooncards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__)) sio.emit('highnooncards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event @sio.event
@bang_handler
def get_foccards(sid): def get_foccards(sid):
import bang.expansions.fistful_of_cards.card_events as ce import bang.expansions.fistful_of_cards.card_events as ce
chs = [] chs = []
@ -616,6 +727,7 @@ def get_foccards(sid):
sio.emit('foccards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__)) sio.emit('foccards_info', room=sid, data=json.dumps(chs, default=lambda o: o.__dict__))
@sio.event @sio.event
@bang_handler
def get_goldrushcards(sid): def get_goldrushcards(sid):
import bang.expansions.gold_rush.shop_cards as grc import bang.expansions.gold_rush.shop_cards as grc
cards = grc.get_cards() cards = grc.get_cards()
@ -626,9 +738,34 @@ def get_goldrushcards(sid):
cards = [cards_dict[i] for i in cards_dict] cards = [cards_dict[i] for i in cards_dict]
sio.emit('goldrushcards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__)) sio.emit('goldrushcards_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
@bang_handler
def get_valleyofshadowscards(sid):
import bang.expansions.the_valley_of_shadows.cards as tvos
cards = tvos.get_starting_deck()
cards_dict = {}
for ca in cards:
if ca.name not in cards_dict:
cards_dict[ca.name] = ca
cards = [cards_dict[i] for i in cards_dict]
sio.emit('valleyofshadows_info', room=sid, data=json.dumps(cards, default=lambda o: o.__dict__))
@sio.event
@bang_handler
def discord_auth(sid, data):
res = requests.post('https://discord.com/api/oauth2/token', data={
'client_id': '1059452581027532880',
'client_secret': 'Mc8ZlMQhayzi1eOqWFtGHs3L0iXCzaEu',
'grant_type': 'authorization_code',
'redirect_uri': data['origin'],
'code': data['code'],
})
if res.status_code == 200:
sio.emit('discord_auth_succ', room=sid, data=res.json())
def pool_metrics(): def pool_metrics():
sio.sleep(60) sio.sleep(60)
Metrics.send_metric('lobbies', points=[len([g for g in games if not g.is_replay])]) Metrics.send_metric('lobbies', points=[sum(not g.is_replay for g in games)])
Metrics.send_metric('online_players', points=[online_players]) Metrics.send_metric('online_players', points=[online_players])
pool_metrics() pool_metrics()
@ -650,6 +787,10 @@ class CustomProxyFix(object):
return [''] return ['']
return self.app(environ, start_response) return self.app(environ, start_response)
discord_ci = '1059452581027532880'
discord_cs = 'Mc8ZlMQhayzi1eOqWFtGHs3L0iXCzaEu'
if __name__ == '__main__': if __name__ == '__main__':
sio.start_background_task(pool_metrics) sio.start_background_task(pool_metrics)
eventlet.wsgi.server(eventlet.listen(('', 5001)), CustomProxyFix(app)) eventlet.wsgi.server(eventlet.listen(('', 5001)), CustomProxyFix(app))

View File

@ -297,8 +297,6 @@ def test_SlabTheKiller():
g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name) g.players[g.turn].play_card(0, g.players[(g.turn+1)%2].name)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.RESPOND assert g.players[(g.turn+1)%2].pending_action == PendingAction.RESPOND
g.players[(g.turn+1)%2].respond(0) g.players[(g.turn+1)%2].respond(0)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.RESPOND
g.players[(g.turn+1)%2].respond(-1)
assert g.players[(g.turn+1)%2].pending_action == PendingAction.WAIT assert g.players[(g.turn+1)%2].pending_action == PendingAction.WAIT
assert g.players[(g.turn+1)%2].lives == 3 assert g.players[(g.turn+1)%2].lives == 3

View File

@ -0,0 +1,142 @@
from random import randint
from bang.characters import Character
from bang.expansions.the_valley_of_shadows.cards import *
from tests.dummy_socket import DummySocket
from bang.deck import Deck
from bang.game import Game
from bang.players import Player, PendingAction
import bang.cards as cs
# test UltimoGiro
def test_ultimo_giro():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
ultimo_giro_guy = g.players[g.turn]
ultimo_giro_guy.draw('')
ultimo_giro_guy.lives = 3
ultimo_giro_guy.hand = [UltimoGiro(0,0)]
assert ultimo_giro_guy.lives == 3
ultimo_giro_guy.play_card(0)
assert ultimo_giro_guy.lives == 4
# test Tomahawk
def test_tomahawk():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(6)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
tomahawk_guy = g.players[g.turn]
tomahawk_guy.draw('')
tomahawk_guy.hand = [Tomahawk(0,0)]
assert len(tomahawk_guy.hand) == 1
tomahawk_guy.play_card(0, g.players[(g.turn+3)%6].name)
assert len(tomahawk_guy.hand) == 1
tomahawk_guy.play_card(0, g.players[(g.turn+1)%6].name)
assert len(tomahawk_guy.hand) == 0
# test Fantasma
def test_fantasma():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
fantasma_guy = g.players[g.turn]
fantasma_guy.lives = 0
fantasma_guy.notify_self()
pl = g.players[g.turn]
pl.draw('')
pl.hand = [Fantasma(0,0)]
pl.play_card(0)
assert pl.pending_action == PendingAction.CHOOSE
assert pl.available_cards[0]['name'] == fantasma_guy.name
pl.choose(0)
assert pl.pending_action == PendingAction.PLAY
assert len(fantasma_guy.equipment) == 1 and isinstance(fantasma_guy.equipment[0], Fantasma)
# test SerpenteASonagli
def test_serpente_a_sonagli():
sio = DummySocket()
g = Game('test', sio)
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
p = g.players[g.turn]
serp = g.players[(g.turn+1)%3]
p.draw('')
p.hand = [SerpenteASonagli(0,0)]
assert len(p.hand) == 1
p.play_card(0, serp.name)
assert len(p.hand) == 0
assert len(serp.equipment) == 1 and isinstance(serp.equipment[0], SerpenteASonagli)
p.end_turn()
assert serp.pending_action == PendingAction.PICK
g.deck.cards[0] = Bang(Suit.SPADES, 5)
serp.pick()
assert serp.lives == 3
serp.draw('')
serp.hand = [SerpenteASonagli(0,0)]
serp.play_card(0, g.players[(g.turn+1)%3].name)
assert len(serp.hand) == 0
serp.end_turn()
assert g.players[g.turn].pending_action == PendingAction.PICK
g.deck.cards[0] = Bang(Suit.HEARTS, 5)
g.players[g.turn].pick()
assert g.players[g.turn].lives == 4
# test RitornoDiFiamma
def test_ritorno_di_fiamma():
sio = DummySocket()
g = Game('test', sio)
g.expansions = ['the_valley_of_shadows']
ps = [Player(f'p{i}', f'p{i}', sio) for i in range(3)]
for p in ps:
g.add_player(p)
g.start_game()
for p in ps:
p.available_characters = [Character('test_char', 4)]
p.set_character(p.available_characters[0].name)
p = g.players[g.turn]
p1 = g.players[(g.turn+1)%3]
p.draw('')
p.hand = [Bang(1, 1)]
p1.hand = [RitornoDiFiamma(0,0)]
p.play_card(0, p1.name)
assert len(p.hand) == 0
assert len(p1.hand) == 1
p1.respond(0)
assert len(p1.hand) == 0
assert p.lives == 3
p.end_turn()
p1.draw('')
p1.hand = [Bang(1, 1)]
p.equipment = [cs.Barile(0,0)]
p.hand = [RitornoDiFiamma(0,0)]
p1.play_card(0, p.name)
assert p.pending_action == PendingAction.PICK
g.deck.cards[0] = Bang(Suit.SPADES, 5)
p.pick()
assert p.pending_action == PendingAction.RESPOND
p.respond(0)
assert p1.lives == 3

View File

@ -17,7 +17,8 @@
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-i18n": "^8.27.1", "vue-i18n": "^8.27.1",
"vue-router": "^3.5.4", "vue-json-viewer": "^2.2.22",
"vue-router": "^3.5.3",
"vue-socket.io": "^3.0.10" "vue-socket.io": "^3.0.10"
}, },
"devDependencies": { "devDependencies": {

View File

@ -135,6 +135,7 @@ export default {
this.$i18n.locale = userLang.split('-')[0] this.$i18n.locale = userLang.split('-')[0]
} }
this.detectColorScheme() this.detectColorScheme()
if (window.location.origin.indexOf('localhost') !== -1) return;
datadogRum.init({ datadogRum.init({
applicationId: '076b1a5e-16a9-44eb-b320-27afd32c57a5', applicationId: '076b1a5e-16a9-44eb-b320-27afd32c57a5',
clientToken: 'pub1cc4d0d6ea0a7235aa1eab86e7a192d4', clientToken: 'pub1cc4d0d6ea0a7235aa1eab86e7a192d4',

View File

@ -1,7 +1,8 @@
<template> <template>
<div :class="{ card: true, equipment: card.is_equipment, character:card.is_character, back:card.is_back, 'usable-next-turn':card.usable_next_turn, 'must-be-used':card.must_be_used, 'gold-rush': card.expansion === 'gold_rush', 'brown':card.kind === 0, 'black':card.kind === 1,}"> <div :class="{ card: true, avatarred:card.avatar, equipment: card.is_equipment, character:card.is_character, back:card.is_back, 'usable-next-turn':card.usable_next_turn, 'must-be-used':card.must_be_used, 'gold-rush': card.expansion === 'gold_rush', 'brown':card.kind === 0, 'black':card.kind === 1,}">
<h4>{{cardName}}</h4> <h4>{{cardName}}</h4>
<div class="emoji">{{emoji}}</div> <div v-if="card.avatar" class="avatar" :style="`background-image: url(${card.avatar});`"></div>
<div :class="{emoji:true, bottomed:card.avatar}">{{emoji}}</div>
<div class="alt_text">{{card.alt_text}}</div> <div class="alt_text">{{card.alt_text}}</div>
<div class="suit">{{number}}<span :style="`${(card.suit !== undefined && card.suit%2 === 0)? 'color:red':''}`">{{suit}}</span></div> <div class="suit">{{number}}<span :style="`${(card.suit !== undefined && card.suit%2 === 0)? 'color:red':''}`">{{suit}}</span></div>
<div class="expansion" v-if="card.expansion_icon">{{card.expansion_icon}}</div> <div class="expansion" v-if="card.expansion_icon">{{card.expansion_icon}}</div>
@ -72,6 +73,12 @@ export default {
word-wrap: normal; word-wrap: normal;
/* word-wrap: break-word; */ /* word-wrap: break-word; */
} }
.avatarred {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
}
.card.back{ .card.back{
color:white; color:white;
background: repeating-linear-gradient( background: repeating-linear-gradient(
@ -117,6 +124,17 @@ export default {
); );
border: 2pt solid rgb(50 122 172); border: 2pt solid rgb(50 122 172);
} }
.avatar {
position: absolute;
width: 36pt;
margin: auto;
top: 25%;
background-position: center;
background-size: contain;
background-repeat: no-repeat;
border-radius: 36pt;
height: 36pt;
}
.card.brown.gold-rush { .card.brown.gold-rush {
box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa; box-shadow: 0 0 0pt 4pt var(--bg-color), 0 0 5pt 4pt #aaa;
border: 2pt dotted #9C7340; border: 2pt dotted #9C7340;
@ -142,6 +160,10 @@ export default {
font-size:26pt; font-size:26pt;
top: 35%; top: 35%;
} }
.emoji.bottomed {
top: 45%;
left: 8pt;
}
.card.must-be-used { .card.must-be-used {
filter: drop-shadow(0 0 5px red); filter: drop-shadow(0 0 5px red);
} }
@ -155,7 +177,7 @@ export default {
left:3pt; left:3pt;
} }
.card.character .suit { .card.character .suit {
font-size: x-small; font-size: small;
right: 3pt; right: 3pt;
text-align: center; text-align: center;
} }

View File

@ -9,7 +9,10 @@
</div> </div>
<div class="cont"> <div class="cont">
<transition-group name="message" tag="div" id="chatbox" :style="`${collapsed?'display:none':''}`"> <transition-group name="message" tag="div" id="chatbox" :style="`${collapsed?'display:none':''}`">
<p style="margin:1pt;" class="chat-message" v-for="(msg, i) in messages" v-bind:key="`${i}-c`" :style="`color:${msg.color};background:${msg.bgcolor}${msg.bgcolor?';border-left: medium solid '+msg.color+';padding-left:2pt;':''}`">{{msg.text}}</p> <p style="margin:1pt;" class="chat-message" v-for="(msg, i) in messages" v-bind:key="`${i}-c`" :style="`color:${msg.color};background:${msg.bgcolor}${msg.bgcolor?';border-left: medium solid '+msg.color+';padding-left:2pt;':''}`">
<JsonViewer v-if="msg.type == 'json'" :value="msg.json"/>
<span v-else>{{msg.text}}</span>
</p>
<p class="end" key="end" style="color:#0000">.</p> <p class="end" key="end" style="color:#0000">.</p>
</transition-group> </transition-group>
<div v-if="commandSuggestion.length > 0"> <div v-if="commandSuggestion.length > 0">
@ -35,11 +38,16 @@ import prison_sfx from '@/assets/sounds/prison.mp3'
import turn_sfx from '@/assets/sounds/turn.mp3' import turn_sfx from '@/assets/sounds/turn.mp3'
import death_sfx from '@/assets/sounds/death.mp3' import death_sfx from '@/assets/sounds/death.mp3'
import cash_sfx from '@/assets/sounds/cash.mp3' import cash_sfx from '@/assets/sounds/cash.mp3'
import JsonViewer from 'vue-json-viewer'
export default { export default {
name: 'Chat', name: 'Chat',
props: { props: {
username: String username: String
}, },
components: {
JsonViewer
},
data: () => ({ data: () => ({
messages: [], messages: [],
toasts: [], toasts: [],
@ -59,7 +67,6 @@ export default {
}, },
sockets: { sockets: {
chat_message(msg) { chat_message(msg) {
// console.log(msg)
if ((typeof msg === "string" && msg.indexOf('_') === 0) || (msg.color != null && msg.text.indexOf('_') === 0)) { if ((typeof msg === "string" && msg.indexOf('_') === 0) || (msg.color != null && msg.text.indexOf('_') === 0)) {
let t_color = null let t_color = null
let bg_color = null let bg_color = null
@ -107,6 +114,9 @@ export default {
} else { // a chat message } else { // a chat message
(new Audio(message_sfx)).play(); (new Audio(message_sfx)).play();
this.messages.push(msg); this.messages.push(msg);
if (msg.type && msg.type === 'json') {
msg.json = JSON.parse(msg.text);
}
if (this.collapsed || window.innerWidth < 1000) { if (this.collapsed || window.innerWidth < 1000) {
this.toasts.push(msg); this.toasts.push(msg);
setTimeout(() => this.toasts.shift(), 5000); setTimeout(() => this.toasts.shift(), 5000);

View File

@ -3,11 +3,12 @@
<h1>{{text}}</h1> <h1>{{text}}</h1>
<div> <div>
<transition-group name="list" tag="div"> <transition-group name="list" tag="div">
<Card v-for="(c, i) in cards" v-bind:key="c.name ? (c.name+c.number) : i" :card="c" @click.native="select(c)" @pointerenter.native="showDesc(c)" @pointerleave.native="desc=''"/> <Card v-for="(c, i) in cards" v-bind:key="c.name ? (c.name+c.number) : i" :card="c" @click.native="internalSelect(c)" @pointerenter.native="showDesc(c)" @pointerleave.native="desc=''"/>
</transition-group> </transition-group>
</div> </div>
<p v-if="hintText">{{hintText}}</p> <p v-if="hintText">{{hintText}}</p>
<div style="margin-top:6pt;" class="button center-stuff" v-if="showCancelBtn" @click="cancel"><span>{{realCancelText}}</span></div> <h2 v-if="timer > 0 && remainingTime > 0 && !showCancelBtn">{{remainingTime}}</h2>
<div style="margin-top:6pt;" class="button center-stuff" v-if="showCancelBtn" @click="internalCancel"><span>{{realCancelText}}</span> <span v-if="timer > 0 && remainingTime > 0"> ({{remainingTime}})</span></div>
<p v-if="desc" style="bottom:10pt;right:0;left:0;position:absolute;margin:16pt;font-size:18pt">{{desc}}</p> <p v-if="desc" style="bottom:10pt;right:0;left:0;position:absolute;margin:16pt;font-size:18pt">{{desc}}</p>
</div> </div>
</template> </template>
@ -29,13 +30,19 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
timer: {
type: Number,
default: 0,
},
text: String, text: String,
hintText: String, hintText: String,
playAudio: Boolean, playAudio: Boolean,
}, },
data: () => ({ data: () => ({
desc: '', desc: '',
realCancelText: '' realCancelText: '',
remainingTime: 0,
intervalID: '',
}), }),
computed: { computed: {
showCancelBtn() { showCancelBtn() {
@ -49,15 +56,29 @@ export default {
//console.log(card) //console.log(card)
if (card.noDesc || card.name == null || card.name == "PewPew!") if (card.noDesc || card.name == null || card.name == "PewPew!")
this.desc = "" this.desc = ""
else if (card.is_character)
this.desc = card.name
else if (card.goal) else if (card.goal)
this.desc = this.$t(`cards.${card.name}.name`) this.desc = this.$t(`cards.${card.name}.name`)
else if (card.desc) else if (card.desc)
this.desc = (this.$i18n.locale=='it'?card.desc:card.desc_eng) this.desc = (this.$i18n.locale=='it'?card.desc:card.desc_eng)
else else
this.desc = this.$t(`cards.${card.name}.desc`) this.desc = this.$t(`cards.${card.name}.desc`)
} },
countDown() {
if (this.remainingTime > 0) {
this.remainingTime--;
} else {
this.select(this.cards[0]);
window.clearInterval(this.intervalID);
}
},
internalCancel() {
if (this.intervalID) window.clearInterval(this.intervalID);
this.cancel();
},
internalSelect(card) {
if (this.intervalID) window.clearInterval(this.intervalID);
this.select(card);
},
}, },
mounted() { mounted() {
this.realCancelText = this.cancelText this.realCancelText = this.cancelText
@ -70,6 +91,11 @@ export default {
if (this.playAudio) { if (this.playAudio) {
(new Audio(show_sfx)).play(); (new Audio(show_sfx)).play();
} }
this.remainingTime = this.timer;
if (this.timer != 0 && this.remainingTime == this.timer) {
if (this.intervalID) window.clearInterval(this.intervalID);
this.intervalID = window.setInterval(this.countDown, 1000);
}
}, },
} }
</script> </script>

View File

@ -108,6 +108,15 @@
</div> </div>
</div> </div>
</div> </div>
<h2 id="valleyofshadowscards">{{$t('help.valleyofshadowscards')}}</h2>
<div class="flexy-cards-wrapper">
<div v-for="(c, i) in valleyofshadowscards" v-bind:key="c.name ? (c.name+c.number) : i" class="flexy-cards">
<Card :card="c" class="valley-of-shadows" @pointerenter.native="''" @pointerleave.native="''"/>
<div style="margin-left:6pt;">
<p>{{$t(`cards.${c.name}.desc`)}}</p>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
@ -127,6 +136,7 @@ export default {
highnooncards: [], highnooncards: [],
foccards: [], foccards: [],
goldrushcards: [], goldrushcards: [],
valleyofshadowscards: [],
}), }),
computed: { computed: {
endTurnCard() { endTurnCard() {
@ -161,6 +171,11 @@ export default {
...x, ...x,
})) }))
}, },
valleyofshadows_info(cardsJson) {
this.valleyofshadowscards = JSON.parse(cardsJson).map(x=>({
...x,
}))
},
}, },
mounted() { mounted() {
this.$socket.emit('get_cards') this.$socket.emit('get_cards')
@ -168,6 +183,7 @@ export default {
this.$socket.emit('get_highnooncards') this.$socket.emit('get_highnooncards')
this.$socket.emit('get_foccards') this.$socket.emit('get_foccards')
this.$socket.emit('get_goldrushcards') this.$socket.emit('get_goldrushcards')
this.$socket.emit('get_valleyofshadowscards')
document.getElementById('help').scrollIntoView(); document.getElementById('help').scrollIntoView();
} }
} }

View File

@ -42,7 +42,7 @@
<div v-if="showTurnFlow" id="turn-indicator" :class="{reversed:turnReversed}"/> <div v-if="showTurnFlow" id="turn-indicator" :class="{reversed:turnReversed}"/>
<transition-group name="list" tag="div" class="players-table"> <transition-group name="list" tag="div" class="players-table">
<Card v-if="startGameCard" key="_start_game_" :donotlocalize="true" :card="startGameCard" @click.native="startGame"/> <Card v-if="startGameCard" key="_start_game_" :donotlocalize="true" :card="startGameCard" @click.native="startGame"/>
<div v-for="p in playersTable" v-bind:key="p.card.name" style="position:relative;"> <div v-for="p in playersTable" v-bind:key="p.card.name" style="position:relative;" class="player-in-table">
<transition-group v-if="p.gold_nuggets && p.gold_nuggets > 0" name="list" tag="div" style="position: absolute;top: -10pt; font-size:9pt;"> <transition-group v-if="p.gold_nuggets && p.gold_nuggets > 0" name="list" tag="div" style="position: absolute;top: -10pt; font-size:9pt;">
<span v-for="(n, i) in p.gold_nuggets" v-bind:key="i" :alt="i">💵</span> <span v-for="(n, i) in p.gold_nuggets" v-bind:key="i" :alt="i">💵</span>
</transition-group> </transition-group>
@ -70,6 +70,7 @@
<span>🤖</span> <span>🤖</span>
</div> </div>
</div> </div>
<Card v-if="startGameCard" key="_shuffle_players_" :donotlocalize="true" :card="shufflePlayersCard" @click.native="shufflePlayers" class="fistful-of-cards"/>
</transition-group> </transition-group>
</div> </div>
<div v-if="started"> <div v-if="started">
@ -85,6 +86,12 @@
<transition name="bounce"> <transition name="bounce">
<full-screen-input v-if="!started && hasToSetUsername" :defaultValue="storedUsername" :text="$t('choose_username')" :val="username" :send="setUsername" :sendText="$t('ok')"/> <full-screen-input v-if="!started && hasToSetUsername" :defaultValue="storedUsername" :text="$t('choose_username')" :val="username" :send="setUsername" :sendText="$t('ok')"/>
</transition> </transition>
<transition name="bounce">
<div v-if="displayAdminStatus" id="admin-status">
<input type="button" @click="displayAdminStatus = false" value="close"/>
<Status deploy_key="ok"/>
</div>
</transition>
</div> </div>
</template> </template>
@ -98,6 +105,7 @@ import Player from './Player.vue'
import Deck from './Deck.vue' import Deck from './Deck.vue'
import TinyHand from './TinyHand.vue' import TinyHand from './TinyHand.vue'
import FullScreenInput from './FullScreenInput.vue' import FullScreenInput from './FullScreenInput.vue'
import Status from './Status.vue'
export default { export default {
name: 'Lobby', name: 'Lobby',
@ -109,7 +117,8 @@ export default {
Deck, Deck,
TinyHand, TinyHand,
PrettyCheck, PrettyCheck,
FullScreenInput FullScreenInput,
Status
}, },
data: () => ({ data: () => ({
username: '', username: '',
@ -134,6 +143,7 @@ export default {
debug_mode: false, debug_mode: false,
showTurnFlow: false, showTurnFlow: false,
turnReversed: false, turnReversed: false,
displayAdminStatus: false,
turn: -1, turn: -1,
}), }),
sockets: { sockets: {
@ -156,6 +166,7 @@ export default {
name: x.name, name: x.name,
ready: x.ready, ready: x.ready,
is_bot: x.is_bot, is_bot: x.is_bot,
avatar: x.avatar,
ncards: 0, ncards: 0,
} }
}) })
@ -179,6 +190,9 @@ export default {
this.username = username this.username = username
// this.$socket.emit('get_cards', 'dodge_city') // this.$socket.emit('get_cards', 'dodge_city')
}, },
mount_status() {
this.displayAdminStatus = true
},
// cards_info(data) { // cards_info(data) {
// data = JSON.parse(data) // data = JSON.parse(data)
// let bigthing = {} // let bigthing = {}
@ -227,7 +241,7 @@ export default {
return '' return ''
}, },
isRoomOwner() { isRoomOwner() {
return this.players.length > 0 && this.players[0].name == this.username return this.players.length > 0 && this.players.filter(x => !x.is_bot)[0].name == this.username
}, },
startGameCard() { startGameCard() {
if (!this.started && this.players.length > 2 && this.isRoomOwner) { if (!this.started && this.players.length > 2 && this.isRoomOwner) {
@ -240,6 +254,16 @@ export default {
} }
return null; return null;
}, },
shufflePlayersCard() {
if (!this.started && this.players.length > 2 && this.isRoomOwner) {
return {
name: this.$t('shuffle_players'),
icon: '🔀',
is_equipment: true,
}
}
return null;
},
playersTable() { playersTable() {
if (Vue.config.devtools) if (Vue.config.devtools)
console.log('update players') console.log('update players')
@ -289,19 +313,25 @@ export default {
}, },
getPlayerCard(player) { getPlayerCard(player) {
let icon = '' let icon = ''
let owner = this.players.filter(x => !x.is_bot)[0];
if (!this.started) icon = '🤠' if (!this.started) icon = '🤠'
else icon = player.ready !== undefined ? ((player.ready)?'👍': '🤔') : (player.is_sheriff ? '⭐' : player.icon) else icon = player.ready !== undefined ? ((player.ready)?'👍': '🤔') : (player.is_sheriff ? '⭐' : player.icon)
return { return {
name: player.name, name: player.name,
number: ((this.username == player.name) ? this.$t('you') : (this.players[0].name == player.name) ? this.$t('owner') :'') + (player.dist ? `${player.dist}` : ''), number: ((this.username == player.name) ? this.$t('you') : (owner.name == player.name) ? this.$t('owner') :'') + (player.dist ? `${player.dist}` : ''),
icon: icon, icon: icon,
is_character: true, is_character: true,
avatar: player.avatar,
} }
}, },
startGame() { startGame() {
this.started = true; this.started = true;
this.$socket.emit('start_game') this.$socket.emit('start_game')
}, },
shufflePlayers() {
this.started = true;
this.$socket.emit('shuffle_players')
},
choose(player_name) { choose(player_name) {
if (Vue.config.devtools) if (Vue.config.devtools)
console.log('choose from' + player_name) console.log('choose from' + player_name)
@ -345,7 +375,7 @@ export default {
if (name.trim().length > 0){ if (name.trim().length > 0){
localStorage.setItem('username', name) localStorage.setItem('username', name)
this.hasToSetUsername = false this.hasToSetUsername = false
this.$socket.emit('set_username', name) this.$socket.emit('set_username', {name:name})
} }
}, },
}, },
@ -369,9 +399,9 @@ export default {
mounted() { mounted() {
if (Vue.config.devtools) if (Vue.config.devtools)
console.log('mounted lobby') console.log('mounted lobby')
if (!this.$route.query.code) if (!this.$route.query.code && !this.$route.query.replay)
return this.$router.push('/') return this.$router.push('/')
this.$socket.emit('get_me', {name:this.$route.query.code, password:this.$route.query.pwd, username: localStorage.getItem('username')}) this.$socket.emit('get_me', {name:this.$route.query.code, password:this.$route.query.pwd, username: localStorage.getItem('username'), discord_token: localStorage.getItem('discord_token'), replay: this.$route.query.replay})
}, },
} }
</script> </script>
@ -426,6 +456,9 @@ export default {
justify-content: space-evenly; justify-content: space-evenly;
margin-bottom: 12pt; margin-bottom: 12pt;
} }
#admin-status {
position:absolute;width:100%;height:100%;overflow:auto;background:var(--bg-color); opacity: 0.8;
}
#turn-indicator{ #turn-indicator{
position: absolute; position: absolute;
width: 100%; width: 100%;
@ -471,5 +504,11 @@ background-position-x: 80px;
min-width: 25vw; min-width: 25vw;
max-width: 25vw; max-width: 25vw;
} }
.player-in-table {
transition: all 0.2s ease-in-out;
}
.player-in-table:hover {
transform: translateY(-5px) scale(1.05);
}
} }
</style> </style>

View File

@ -18,6 +18,7 @@
<input id="username" v-model="username" /> <input id="username" v-model="username" />
<input type="submit" class="btn" :value="$t('submit')"/> <input type="submit" class="btn" :value="$t('submit')"/>
</form> </form>
<a class="btn" :href="redirectUrl">Login with Discord</a>
<p v-if="onlinePlayers > 0">{{$t("online_players")}}{{onlinePlayers}}</p> <p v-if="onlinePlayers > 0">{{$t("online_players")}}{{onlinePlayers}}</p>
</div> </div>
<div v-else> <div v-else>
@ -71,8 +72,12 @@ export default {
isInLobby: false, isInLobby: false,
onlinePlayers: 0, onlinePlayers: 0,
randomTip: '', randomTip: '',
discordPic: '',
}), }),
computed: { computed: {
redirectUrl() {
return 'https://discordapp.com/api/oauth2/authorize?client_id=1059452581027532880&response_type=code&scope=identify&redirect_uri=' + window.location.origin;
},
noLobbyAvailable() { noLobbyAvailable() {
return this.openLobbies && this.openLobbies.length == 0 return this.openLobbies && this.openLobbies.length == 0
}, },
@ -85,6 +90,7 @@ export default {
number: this.$t('you'), number: this.$t('you'),
icon: '🤠', icon: '🤠',
is_character: true, is_character: true,
avatar: this.discordPic,
} }
}, },
version() { version() {
@ -105,14 +111,20 @@ export default {
players(num) { players(num) {
this.onlinePlayers = num; this.onlinePlayers = num;
// console.log('PLAYERS:' + num) // console.log('PLAYERS:' + num)
} },
discord_auth_succ(data) {
if (data.access_token) {
localStorage.setItem('discord_token', data.access_token)
this.login()
}
},
}, },
methods: { methods: {
setUsername(e){ setUsername(e){
if (this.username.trim().length > 0){ if (this.username.trim().length > 0){
this.didSetUsername = true this.didSetUsername = true
localStorage.setItem('username', this.username) localStorage.setItem('username', this.username)
this.$socket.emit('set_username', this.username) this.$socket.emit('set_username', {name:this.username})
e.preventDefault(); e.preventDefault();
} }
}, },
@ -145,8 +157,34 @@ export default {
init() { init() {
location.reload(); location.reload();
}, },
login() {
fetch('https://discordapp.com/api/users/@me', {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('discord_token')
}
})
.then(response => response.json())
.then(data => {
console.log(data)
this.username = data.username
this.didSetUsername = true
this.discordPic = `https://cdn.discordapp.com/avatars/${data.id}/${data.avatar}.png`
localStorage.setItem('username', this.username)
this.$socket.emit('set_username', {name: this.username, discord_token: localStorage.getItem('discord_token')})
}).catch(err => {
console.error(err)
localStorage.removeItem('discord_token')
this.$router.replace({query: []})
})
}
}, },
mounted() { mounted() {
if (localStorage.getItem('discord_token')) {
this.login()
} else if (this.$route.query.code) {
this.$socket.emit('discord_auth', {code:this.$route.query.code, origin:window.location.origin})
this.$router.replace({query: []})
}
this.randomTip = `tip_${1+Math.floor(Math.random() * 8)}` this.randomTip = `tip_${1+Math.floor(Math.random() * 8)}`
if (localStorage.getItem('username')) if (localStorage.getItem('username'))
this.username = localStorage.getItem('username') this.username = localStorage.getItem('username')

View File

@ -37,6 +37,7 @@
</div> </div>
<div v-if="lives > 0 || is_ghost" style="position:relative"> <div v-if="lives > 0 || is_ghost" style="position:relative">
<span id="hand_text">{{$t('hand')}}</span> <span id="hand_text">{{$t('hand')}}</span>
<span id="hand_text" style="bottom:40pt;">{{hand.length}}/{{maxHandLength()}}</span>
<transition-group name="list" tag="div" :class="{hand:true, 'play-cards':pending_action===2}"> <transition-group name="list" tag="div" :class="{hand:true, 'play-cards':pending_action===2}">
<Card v-for="card in handComputed" v-bind:key="card.name+card.number+card.suit" :card="card" <Card v-for="card in handComputed" v-bind:key="card.name+card.number+card.suit" :card="card"
@click.native="play_card(card, false)" @click.native="play_card(card, false)"
@ -49,15 +50,15 @@
</transition> </transition>
<Chooser v-if="is_my_turn && pending_action == 4 && (lives > 0 || is_ghost) && !(emporioCards && emporioCards.cards && emporioCards.cards.length > 0)" :text="$t('wait')" :cards="[]"/> <Chooser v-if="is_my_turn && pending_action == 4 && (lives > 0 || is_ghost) && !(emporioCards && emporioCards.cards && emporioCards.cards.length > 0)" :text="$t('wait')" :cards="[]"/>
<Chooser v-if="card_against" :text="$t('card_against')" :hint-text="visiblePlayers.length === 0 ? $t('no_players_in_range'):''" :cards="visiblePlayers" :select="selectAgainst" :cancel="card_against.number !== 42 ? cancelCardAgainst : null"/> <Chooser v-if="card_against" :text="$t('card_against')" :hint-text="visiblePlayers.length === 0 ? $t('no_players_in_range'):''" :cards="visiblePlayers" :select="selectAgainst" :cancel="card_against.number !== 42 ? cancelCardAgainst : null"/>
<Chooser v-if="pending_action == 3" :text="respondText" :cards="respondCards" :select="respond" :playAudio="true"/> <Chooser v-if="pending_action == 3" :text="respondText" :cards="respondCards" :select="respond" :playAudio="true" :timer="30"/>
<Chooser v-if="shouldChooseCard" :text="$t(choose_text)" :cards="available_cards" :select="choose" :playAudio="true"/> <Chooser v-if="shouldChooseCard" :text="$t(choose_text)" :cards="available_cards" :select="choose" :playAudio="true" :timer="30"/>
<Chooser v-if="lives <= 0 && max_lives > 0 && !is_ghost && !spectator" :text="$t('you_died')" :cancelText="$t('spectate')" :cancel="()=>{max_lives = 0; spectator = true}"/> <Chooser v-if="lives <= 0 && max_lives > 0 && !is_ghost && !spectator" :text="$t('you_died')" :cancelText="$t('spectate')" :cancel="()=>{max_lives = 0; spectator = true}"/>
<Chooser v-if="win_status !== undefined" :text="win_status?$t('you_win'):$t('you_lose')" /> <Chooser v-if="win_status !== undefined" :text="win_status?$t('you_win'):$t('you_lose')" />
<Chooser v-if="show_role" :text="$t('you_are')" :cards="[my_role]" :hintText="($i18n.locale=='it'?my_role.goal:my_role.goal_eng)" :select="() => {show_role=false}" :cancel="() => {show_role=false}" :cancelText="$t('ok')" /> <Chooser v-if="show_role" :text="$t('you_are')" :cards="[my_role]" :hintText="($i18n.locale=='it'?my_role.goal:my_role.goal_eng)" :select="() => {show_role=false}" :cancel="() => {show_role=false}" :cancelText="$t('ok')"/>
<Chooser v-if="notifycard" :key="notifycard.card" :text="`${notifycard.player} ${$t('did_pick_as')}:`" :cards="[notifycard.card]" :hintText="$t(notifycard.message)" class="turn-notify-4s"/> <Chooser v-if="notifycard" :key="notifycard.card" :text="`${notifycard.player} ${$t('did_pick_as')}:`" :cards="[notifycard.card]" :hintText="$t(notifycard.message)" class="turn-notify-4s"/>
<Chooser v-if="cantplaycard" :key="cantplaycard" :text="`${$t('cantplaycard')}`" class="turn-notify-4s"/> <Chooser v-if="cantplaycard" :key="cantplaycard" :text="`${$t('cantplaycard')}`" class="turn-notify-4s"/>
<Chooser v-if="!show_role && is_my_turn && pending_action < 2" :text="$t('play_your_turn')" :key="is_my_turn" class="turn-notify" /> <Chooser v-if="!show_role && is_my_turn && pending_action < 2" :text="$t('play_your_turn')" :key="is_my_turn" class="turn-notify" />
<Chooser v-if="!show_role && availableCharacters.length > 0" :text="$t('choose_character')" :cards="availableCharacters" :select="setCharacter"/> <Chooser v-if="!show_role && availableCharacters.length > 0" :text="$t('choose_character')" :cards="availableCharacters" :select="setCharacter" :timer="45"/>
<Chooser v-if="hasToPickResponse" :playAudio="true" :text="`${$t('pick_a_card')} ${attacker?($t('to_defend_from')+' '+attacker):''}`" :key="hasToPickResponse" class="turn-notify" /> <Chooser v-if="hasToPickResponse" :playAudio="true" :text="`${$t('pick_a_card')} ${attacker?($t('to_defend_from')+' '+attacker):''}`" :key="hasToPickResponse" class="turn-notify" />
<Chooser v-if="!card_against && card_with" :text="`${$t('choose_scarp_card_to')} ${card_with.name.toUpperCase()}`" :cards="handComputed.filter(x => x !== card_with)" :select="selectWith" :cancel="()=>{card_with = null}"/> <Chooser v-if="!card_against && card_with" :text="`${$t('choose_scarp_card_to')} ${card_with.name.toUpperCase()}`" :cards="handComputed.filter(x => x !== card_with)" :select="selectWith" :cancel="()=>{card_with = null}"/>
<Chooser v-if="showScrapScreen" :text="`${$t('discard')} ${hand.length}/${maxHandLength()}`" :cards="hand" :select="scrap" :cancel="cancelEndingTurn"/> <Chooser v-if="showScrapScreen" :text="`${$t('discard')} ${hand.length}/${maxHandLength()}`" :cards="hand" :select="scrap" :cancel="cancelEndingTurn"/>
@ -69,6 +70,7 @@
:cards="notScrappedHand" :select="holydayScrapAdd" :cancel="() => {holydayScrap = false;scrapHand=[]}"/> :cards="notScrappedHand" :select="holydayScrapAdd" :cancel="() => {holydayScrap = false;scrapHand=[]}"/>
<Chooser v-if="holydayScrap && scrapHand.length == 2" :text="$t('card_against')" :cards="visiblePlayers" :select="holydayScrapBang" :cancel="() => {holydayScrap = false;scrapHand=[]}"/> <Chooser v-if="holydayScrap && scrapHand.length == 2" :text="$t('card_against')" :cards="visiblePlayers" :select="holydayScrapBang" :cancel="() => {holydayScrap = false;scrapHand=[]}"/>
<Chooser style="filter: grayscale(1);" v-if="emporioCards && emporioCards.cards && emporioCards.cards.length > 0 && (pending_action === 4 || pending_action === null)" :text="$t('emporio_others', [emporioCards.name])" :cards="emporioCards.cards"/> <Chooser style="filter: grayscale(1);" v-if="emporioCards && emporioCards.cards && emporioCards.cards.length > 0 && (pending_action === 4 || pending_action === null)" :text="$t('emporio_others', [emporioCards.name])" :cards="emporioCards.cards"/>
<div style="position: fixed;width: 100%;height: 100%;background: #ff000070;top: 0;left: 0;" v-if="hurt" class="hurt-notify"/>
</div> </div>
</template> </template>
@ -134,6 +136,7 @@ export default {
committed_suit_manette: null, committed_suit_manette: null,
gold_nuggets: 0, gold_nuggets: 0,
cantplaycard: false, cantplaycard: false,
hurt: false,
}), }),
sockets: { sockets: {
role(role) { role(role) {
@ -205,6 +208,12 @@ export default {
this.notifycard = null this.notifycard = null
}.bind(this), 4000) }.bind(this), 4000)
}, },
hurt() {
this.hurt = true
setTimeout(function(){
this.hurt = false
}.bind(this), 500)
},
cant_play_card() { cant_play_card() {
this.cantplaycard = true this.cantplaycard = true
setTimeout(function(){ setTimeout(function(){
@ -234,6 +243,7 @@ export default {
name: player.name, name: player.name,
number: player.dist !== undefined ? `${player.dist}` : '', number: player.dist !== undefined ? `${player.dist}` : '',
icon: this.noStar ? player.icon : player.is_sheriff ? '⭐' : '🤠', icon: this.noStar ? player.icon : player.is_sheriff ? '⭐' : '🤠',
avatar: player.avatar,
is_character: true, is_character: true,
}}) }})
return vis return vis
@ -255,6 +265,7 @@ export default {
number: player.dist !== undefined ? `${player.dist}` : '', number: player.dist !== undefined ? `${player.dist}` : '',
icon: this.noStar ? player.icon : player.is_sheriff ? '⭐' : '🤠', icon: this.noStar ? player.icon : player.is_sheriff ? '⭐' : '🤠',
alt_text: Array(player.lives+1).join('❤️')+Array(player.max_lives-player.lives+1).join('💀'), alt_text: Array(player.lives+1).join('❤️')+Array(player.max_lives-player.lives+1).join('💀'),
avatar: player.avatar,
is_character: true, is_character: true,
}}) }})
if (this.card_against && this.card_against.can_target_self) { if (this.card_against && this.card_against.can_target_self) {
@ -510,6 +521,10 @@ export default {
margin: 10pt 0pt; margin: 10pt 0pt;
overflow:auto; overflow:auto;
} }
.hurt-notify {
pointer-events: none;
animation: disappear 0.5s ease-in forwards;
}
.turn-notify { .turn-notify {
pointer-events: none; pointer-events: none;
animation: disappear 2s ease-in forwards; animation: disappear 2s ease-in forwards;

View File

@ -31,9 +31,15 @@
</template> </template>
<script> <script>
export default { export default {
name: 'Help', name: 'Status',
components: { components: {
}, },
props: {
onpage: {
type: Boolean,
default: false
}
},
data:()=>({ data:()=>({
rooms: [], rooms: [],
deploy_key: '' deploy_key: ''
@ -46,7 +52,7 @@ export default {
}, },
}, },
mounted() { mounted() {
if (this.deploy_key == "") if (this.deploy_key == "" && this.onpage)
this.deploy_key = prompt('Write the key'); this.deploy_key = prompt('Write the key');
this.refresh(); this.refresh();
}, },

View File

@ -9,9 +9,10 @@
"tip_7": "If you want to help us translate the game in your language, ping us on discord!", "tip_7": "If you want to help us translate the game in your language, ping us on discord!",
"tip_8": "If you disconnect during in an ongoing game you will be replaced by a bot (while you are gone)!", "tip_8": "If you disconnect during in an ongoing game you will be replaced by a bot (while you are gone)!",
"online_players": "Online players: ", "online_players": "Online players: ",
"shuffle_players": "Shuffle Players",
"choose_username": "Pick an username:", "choose_username": "Pick an username:",
"report_bug":"Write what the bug consists of", "report_bug": "Write what the bug consists of",
"report":"Report a bug", "report": "Report a bug",
"available_lobbies": "Available Lobbies:", "available_lobbies": "Available Lobbies:",
"spectate_lobbies": "Spectate ongoing games:", "spectate_lobbies": "Spectate ongoing games:",
"no_lobby_available": "No lobbies available", "no_lobby_available": "No lobbies available",
@ -56,6 +57,9 @@
"choose_ranch": "Choose the cards to replace", "choose_ranch": "Choose the cards to replace",
"choose_dalton": "Choose which equipment to discard", "choose_dalton": "Choose which equipment to discard",
"choose_fratelli_di_sangue": "Choose who you want to donate one of your lives", "choose_fratelli_di_sangue": "Choose who you want to donate one of your lives",
"choose_fantasma": "Choose who to bring back to life",
"choose_tornado": "Choose a card to discard for the tornado",
"choose_bandidos": "Choose between discarding 2 cards or losing a life (1 card if you only have 1)",
"choose_cecchino": "Choose who to shoot", "choose_cecchino": "Choose who to shoot",
"choose_rimbalzo_player": "Choose the target of the bang", "choose_rimbalzo_player": "Choose the target of the bang",
"choose_rimbalzo_card": "Choose the card to discard the bang to", "choose_rimbalzo_card": "Choose the card to discard the bang to",
@ -103,7 +107,7 @@
"flipped": "{0} flipped a {1} {2}.", "flipped": "{0} flipped a {1} {2}.",
"explode": "{0} blew up the dynamite.", "explode": "{0} blew up the dynamite.",
"beer_save": "{0} used a beer to save his life.", "beer_save": "{0} used a beer to save his life.",
"get_nugget":"{0} got a gold nugget using a Beer.", "get_nugget": "{0} got a gold nugget using a Beer.",
"play_card": "{0} played {1}.", "play_card": "{0} played {1}.",
"purchase_card": "{0} purchased {1}.", "purchase_card": "{0} purchased {1}.",
"play_card_against": "{0} played {1} against {2}.", "play_card_against": "{0} played {1} against {2}.",
@ -132,7 +136,10 @@
"UnionPacific": "{0} played Union Pacific and draws 4 cards from the deck", "UnionPacific": "{0} played Union Pacific and draws 4 cards from the deck",
"use_special": "{0} used the special ability of their character ({1})", "use_special": "{0} used the special ability of their character ({1})",
"gold_rush_pay_discard": "{0} discarded {2} from {1}.", "gold_rush_pay_discard": "{0} discarded {2} from {1}.",
"choose_emporio": "{0} has chosen {1} from General Store." "choose_emporio": "{0} has chosen {1} from General Store.",
"shotgun_scrap": "When the shotgun hit {0} a card flew away from his hand ({1})",
"taglia_reward": "{1} got a card from the bounty on {0}",
"snake_bit": "{0} was bitten by the Rattle Snake."
}, },
"foc": { "foc": {
"leggedelwest": "He must play this card on this turn if possible." "leggedelwest": "He must play this card on this turn if possible."
@ -674,6 +681,66 @@
"Simeon Picos": { "Simeon Picos": {
"name": "Simeon Picos", "name": "Simeon Picos",
"desc": "He gets 1 gold nugget every time he loses 1 hp." "desc": "He gets 1 gold nugget every time he loses 1 hp."
},
"Fantasma": {
"name": "Ghost",
"desc": "Play on any eliminated player: That player is back in the game, but can't gain nor lose life points."
},
"Lemat": {
"name": "Lemat",
"desc": "During your turn you can use any card as BANG!."
},
"SerpenteASonagli": {
"name": "Serpente a Sonagli",
"desc": "Play on any player. At the beginning of his turn. if that player draws on Spades, he loses I life point."
},
"Shotgun": {
"name": "Shotgun",
"desc": "Every time you wound a player, they must discard a card from their hand."
},
"Taglia": {
"name": "Bounty",
"desc": "Play on anyone. If that player is hit by BANG!, the person who shot gets a card from the deck."
},
"Mira": {
"name": "Aim",
"desc": "Play this card along with a BANG! card. If the target is hit, they lose 2 life points."
},
"RitornoDiFiamma": {
"name": "Backfire",
"desc": "Counts as a Missed! card. The player who shot is the target of a BANG!."
},
"Bandidos": {
"name": "Bandidos",
"desc": "Each player chooses to discard 2 cards from their hand (or 1 if they only have 1) or lose 1 life point."
},
"Fuga": {
"name": "Escape",
"desc": "Can be played out of turn. Avoids the effect of a brown (not BANG!) card you are a target of."
},
"Sventagliata": {
"name": "Fanning",
"desc": "Counts as the only BANG! of the turn. A player of your choice at distance 1 from the target (if any, excluding yourself) is also a target of a BANG.."
},
"UltimoGiro": {
"name": "Last Call",
"desc": "Recovers 1 life point"
},
"Poker": {
"name": "Poker",
"desc": "All other players discard a card from their hand, at the same time. If there are no Aces, draw up to 2 of those cards."
},
"Salvo": {
"name": "Saved!",
"desc": "Can be played out of turn. Prevents another player from losing 1 life point. If they survive, they draw 2 cards from their hand or deck (your choice)."
},
"Tomahawk": {
"name": "Tomahawk",
"desc": "Attacks a player up to distance 2."
},
"Tornado": {
"name": "Tornado",
"desc": "Everyone discards a card from their hand (if possible), then draws 2 cards from the deck."
} }
}, },
"help": { "help": {
@ -724,7 +791,8 @@
"gotogoldrush": "Jump to Gold Rush", "gotogoldrush": "Jump to Gold Rush",
"highnooncards": "High Noon - Event Cards", "highnooncards": "High Noon - Event Cards",
"foccards": "Fistful of Cards - Event Cards", "foccards": "Fistful of Cards - Event Cards",
"goldrushcards": "Gold Rush Cards" "goldrushcards": "Gold Rush Cards",
"valleyofshadowscards": "The Valley of Shadows Cards"
}, },
"theme": { "theme": {
"sepia": "Sepia", "sepia": "Sepia",

View File

@ -9,6 +9,7 @@
"tip_7": "Se vuoi aiutarci a tradurre il gioco nella tua lingua scrivicelo su discord!", "tip_7": "Se vuoi aiutarci a tradurre il gioco nella tua lingua scrivicelo su discord!",
"tip_8": "Se ti disconnetti durante una partita verrai sostituito da un bot (durante la tua assenza)!", "tip_8": "Se ti disconnetti durante una partita verrai sostituito da un bot (durante la tua assenza)!",
"online_players": "Giocatori online: ", "online_players": "Giocatori online: ",
"shuffle_players": "Riordina Giocatori",
"choose_username": "Scegli un username:", "choose_username": "Scegli un username:",
"report_bug":"Scrivi in cosa consiste il bug", "report_bug":"Scrivi in cosa consiste il bug",
"report":"Segnala un bug", "report":"Segnala un bug",
@ -56,6 +57,9 @@
"choose_ranch": "Scegli le carte da sostituire", "choose_ranch": "Scegli le carte da sostituire",
"choose_dalton": "Scegli che equipaggiamento scartare", "choose_dalton": "Scegli che equipaggiamento scartare",
"choose_fratelli_di_sangue": "Scegli a chi donare una delle tue vite", "choose_fratelli_di_sangue": "Scegli a chi donare una delle tue vite",
"choose_fantasma": "Scegli chi riportare in vita",
"choose_tornado": "Scegli una carta da scartare per il tornado",
"choose_bandidos": "Scegli tra scartare 2 carte o perdere una vita (1 carta se ne hai solo 1)",
"choose_cecchino": "Scegli contro chi sparare", "choose_cecchino": "Scegli contro chi sparare",
"choose_rimbalzo_player": "Scegli contro chi scartare il bang", "choose_rimbalzo_player": "Scegli contro chi scartare il bang",
"choose_rimbalzo_card": "Scegli contro che carta scartare il bang", "choose_rimbalzo_card": "Scegli contro che carta scartare il bang",
@ -132,7 +136,10 @@
"UnionPacific": "{0} ha giocato Union Pacific e ha pescato 4 carte.", "UnionPacific": "{0} ha giocato Union Pacific e ha pescato 4 carte.",
"use_special": "{0} ha usato l'abilità speciale del suo personaggio ({1})", "use_special": "{0} ha usato l'abilità speciale del suo personaggio ({1})",
"gold_rush_pay_discard": "{0} ha fatto scartare {2} a {1}.", "gold_rush_pay_discard": "{0} ha fatto scartare {2} a {1}.",
"choose_emporio": "{0} ha scelto {1} da Emporio." "choose_emporio": "{0} ha scelto {1} da Emporio.",
"shotgun_scrap": "Quando lo shotgun ha colpito {0} gli ha tolto una carta ({1})",
"taglia_reward": "{1} ha ottenuto ricompensa dalla taglia su {0}",
"snake_bit": "{0} è stato morso dal Serpente a Sonagli."
}, },
"foc": { "foc": {
"leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile" "leggedelwest": "Ed è obbligato a usarla nel suo turno, se possibile"
@ -674,6 +681,66 @@
"Simeon Picos": { "Simeon Picos": {
"name": "Simeon Picos", "name": "Simeon Picos",
"desc": "Ogni volta che viene ferito, prende una pepita" "desc": "Ogni volta che viene ferito, prende una pepita"
},
"Fantasma": {
"name": "Fantasma",
"desc": "Gioca su un giocatore eliminato. Quel giocatore torna in gioco, ma non può guadagnare né perdere punti vita."
},
"Lemat": {
"name": "Lemat",
"desc": "Puoi usare ogni carta in mano come BANG!."
},
"SerpenteASonagli": {
"name": "Serpente a Sonagli",
"desc": "Gioca su chiunque. All'inizio del turno, quel giocatore estrae una carta, e se è Picche, perde 1 punto vita."
},
"Shotgun": {
"name": "Shotgun",
"desc": "Ogni volta che ferisci un giocatore, deve scartare una carta a dalla sua mano."
},
"Taglia": {
"name": "Taglia",
"desc": "Gioca su chiunque. Se quel giocatore è colpito da BANG!, chi ha sparato ottiene una carta dal mazzo."
},
"Mira": {
"name": "Mira",
"desc": "Gioca questa carta assieme a una carta BANG!. Se il bersaglio viene colpito, perde 2 punti vita."
},
"RitornoDiFiamma": {
"name": "Ritorno di Fiamma",
"desc": "Vale una carta Mancato! Il giocatore che ha sparato è bersaglio di un BANG!."
},
"Bandidos": {
"name": "Bandidos",
"desc": "Ogni giocatore sceglie se scartare 2 carte dalla mano (o 1 se ne ha solo 1) o perdere 1 punto vita."
},
"Fuga": {
"name": "Fuga",
"desc": "Può essere giocata fuori turno. Evita l'effetto di una carta marrone (non BANG!) di cui sei uno dei bersagli."
},
"Sventagliata": {
"name": "Sventagliata",
"desc": "Conta come l'unico BANG! del turno. Anche un giocatore a tua scelta a distanza 1 dal bersaglio (se ce, te escluso) è bersaglio di un BANG."
},
"UltimoGiro": {
"name": "Ultimo Giro",
"desc": "Recupera 1 vita"
},
"Poker": {
"name": "Poker",
"desc": "Tutti gli altri scartano una carta dalla mano, allo stesso tempo. Se non c'è alcun Asso, pesca fino a 2 di quelle carte."
},
"Salvo": {
"name": "Salvo!",
"desc": "Può essere giocata fuori turno. Previeni la perdita di 1 punto vita di un altro giocatore. Se sopravvive, pesca 2 carte dalla sua mano o dal mazzo (scegli)."
},
"Tomahawk": {
"name": "Tomahawk",
"desc": "Attacca un giocatore fino a distanza 2."
},
"Tornado": {
"name": "Tornado",
"desc": "Tutti scartano una carta dalla mano (se possibile), poi ottengono 2 carte dal mazzo."
} }
}, },
"help": { "help": {
@ -724,7 +791,8 @@
"allcharacters": "Tutti i personaggi", "allcharacters": "Tutti i personaggi",
"highnooncards": "Carte Evento High Noon", "highnooncards": "Carte Evento High Noon",
"foccards": "Carte Evento Fistful of Cards", "foccards": "Carte Evento Fistful of Cards",
"goldrushcards": "Carte Gold Rush" "goldrushcards": "Carte Gold Rush",
"valleyofshadowscards": "Carte The Valley of Shadows"
}, },
"theme": { "theme": {
"sepia": "Seppia", "sepia": "Seppia",

File diff suppressed because it is too large Load Diff