diff --git a/messages/en.json b/messages/en.json index 2f0a1df..4faf29a 100644 --- a/messages/en.json +++ b/messages/en.json @@ -430,6 +430,7 @@ "werekitten_notify": "You are a \u0002werekitten\u0002. Due to your overwhelming cuteness, the seer always sees you as villager and the gunner will always miss you. Detectives can still reveal your true identity, however. Use \"kill \" to kill a villager.", "warlock_notify": "You are a \u0002{0}warlock\u0002. Each night you can curse someone with \"curse \" to turn them into a cursed villager, so the seer sees them as wolf. Act quickly, as your curse applies as soon as you cast it! Only detectives can reveal your true identity, seers will see you as a regular villager.", "wolf_mystic_notify": "You are a \u0002wolf mystic\u0002. Each night you divine the number of alive good villagers who have a special role. You may also use \"kill \" to kill a villager.", + "wolf_shaman_notify": "You are a \u0002wolf shaman\u0002. You can select a player to receive a totem each night by using \"give \". You may give yourself a totem, but you may not give the same player a totem two nights in a row. If you do not give the totem to anyone, it will be given to a random player. You may also use \"kill \" to kill a villager.", "fallen_angel_notify": "You are a \u0002fallen angel\u0002. Your sharp claws will rend any protection the villagers may have, and will likely kill living guardians as well. Use \"kill \" to kill a villager.", "undefined_role_notify": "You are a{0} \u0002{1}\u0002. There would normally be instructions here, but someone forgot to add them in. Please report this to the admins, you can PM me \"admins\" for a list of available ones.", "wolfchat_notify": "Also, if you PM me, your message will be relayed to other wolves.", @@ -473,6 +474,7 @@ "pestilence_totem": "If the player who is given this totem is killed by wolves tomorrow night, the wolves will not be able to kill the night after.", "retribution_totem": "If the player who is given this totem will die tonight, they also kill anyone who killed them.", "misdirection_totem": "If the player who is given this totem attempts to use a power the following day or night, they will target a player adjacent to their intended target instead of the player they targeted.", + "deceit_totem": "If the player who is given this totem is a seer or an oracle, or is seen by a seer or an oracle, the vision will be shifted. If the person would be seen as wolf, they are instead seen as a villager. Otherwise, they are seen as a wolf.", "generic_bug_totem": "No description for this totem is available. This is a bug, so please report this to the admins.", "shaman_simple": "You are a \u0002{0}\u0002.", "totem_simple": "You have the \u0002{0}\u0002 totem.", @@ -632,6 +634,10 @@ "seer_bit_3": "Something felt strange in that vision you had last night. You saw the shadows of a person, bed-ridden and unable to move. You see they are sick, and probably contagious. They look up, and although you cannot recognize the face through the pock marks, they seem to recognize you. They point at you, and the vision ends.", "succubus_pass": "You have chosen to not entrance anyone tonight.", "seer_turn": "Reflecting on your visions the previous night, you believe that you have discovered a way to make them actually happen! This realization is combined with noting that you seem to have transformed into a werewolf, a fact that doesn't seem to faze you in the slightest. Your face grins with evil resolve as you head out to the forest, in search of the other wolves you were until recently trying so hard to kill.", + "shaman_bit_1": "At the stroke of midnight, you begin the ritual. The circle of totems around you begin glow red with dark power, then suddenly channel all of it into your mind. An immense pressure pushes against you and makes it difficult to think. You cry out in horror as realization hits; it is suppressing your old self instead of driving out the new urges!", + "shaman_bit_2": " Ideally, one dabbling in the dark arts would take time to fully understand every nuance in a ritual. Given the discordant cacophony of thoughts swimming in your head, your desire to fight off your affliction versus baser urges trying to take root, ideal is something you cannot afford. You prepare all night for the ritual and see the sun peeking above the horizon as you finish the preparations.", + "shaman_bit_3": "While you were out last night, you were bit by a fearsome-looking wolf. Knowing what this will do if left unchecked, you search your library for any magics to stop the effects. Coming up empty handed, you become desperate. You were strongly cautioned to avoid the dark arts in school, but it may offer the only solution. It can't be THAT bad; besides, you're an experienced shaman now, not some neophyte.", + "shaman_turn": "With your mind finally free of distraction, you apply your mastery of the dark arts towards making new potent totems, the likes of which you have never even dreamed of before. Right as you finish, the moonlight filters into the room. You grin at it and know instinctively what to do, transforming into a fearsome werewolf. You let loose a howl, then grab your new totem as you set off into the night.", "no_longer_entranced": "You are no longer entranced.", "doomsayer_notify": "You are a \u0002doomsayer\u0002. You can see how bad luck will befall someone at night by using \"see \" on them. You may also use \"kill \" to kill a villager.", "prophet_chance_1": "The first time each night you use your ability, you risk a {0}% chance of having your identity revealed to that person. If your identity is revealed this way, you cannot use your ability again that night. ", diff --git a/src/gamemodes.py b/src/gamemodes.py index 5110a9b..3296b91 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -455,22 +455,23 @@ class RandomMode(GameMode): self.ALPHA_WOLF_NIGHTS = 2 self.TEMPLATE_RESTRICTIONS = {template: frozenset() for template in var.TEMPLATE_RESTRICTIONS} - self.TOTEM_CHANCES = { # shaman , crazed - "death": ( 8 , 1 ), - "protection": ( 6 , 1 ), - "silence": ( 4 , 1 ), - "revealing": ( 2 , 1 ), - "desperation": ( 4 , 1 ), - "impatience": ( 7 , 1 ), - "pacifism": ( 7 , 1 ), - "influence": ( 7 , 1 ), - "narcolepsy": ( 4 , 1 ), - "exchange": ( 1 , 1 ), - "lycanthropy": ( 1 , 1 ), - "luck": ( 6 , 1 ), - "pestilence": ( 3 , 1 ), - "retribution": ( 5 , 1 ), - "misdirection": ( 6 , 1 ), + self.TOTEM_CHANCES = { # shaman , crazed , wolf + "death": ( 8 , 1 , 1 ), + "protection": ( 6 , 1 , 6 ), + "silence": ( 4 , 1 , 3 ), + "revealing": ( 2 , 1 , 5 ), + "desperation": ( 4 , 1 , 7 ), + "impatience": ( 7 , 1 , 2 ), + "pacifism": ( 7 , 1 , 2 ), + "influence": ( 7 , 1 , 2 ), + "narcolepsy": ( 4 , 1 , 3 ), + "exchange": ( 1 , 1 , 1 ), + "lycanthropy": ( 1 , 1 , 3 ), + "luck": ( 6 , 1 , 7 ), + "pestilence": ( 3 , 1 , 1 ), + "retribution": ( 5 , 1 , 6 ), + "misdirection": ( 6 , 1 , 4 ), + "deceit": ( 3 , 1 , 6 ), } def startup(self): @@ -520,23 +521,29 @@ class AleatoireMode(GameMode): def __init__(self, arg=""): super().__init__(arg) self.SHARPSHOOTER_CHANCE = 1 - # SHAMAN , CRAZED SHAMAN - self.TOTEM_CHANCES = { "death": ( 4 , 1 ), - "protection": ( 8 , 1 ), - "silence": ( 2 , 1 ), - "revealing": ( 0 , 1 ), - "desperation": ( 1 , 1 ), - "impatience": ( 0 , 1 ), - "pacifism": ( 0 , 1 ), - "influence": ( 0 , 1 ), - "narcolepsy": ( 0 , 1 ), - "exchange": ( 0 , 1 ), - "lycanthropy": ( 0 , 1 ), - "luck": ( 0 , 1 ), - "pestilence": ( 1 , 1 ), - "retribution": ( 4 , 1 ), - "misdirection": ( 0 , 1 ), + # SHAMAN , CRAZED SHAMAN , WOLF SHAMAN + self.TOTEM_CHANCES = { "death": ( 4 , 1 , 0 ), + "protection": ( 8 , 1 , 0 ), + "silence": ( 2 , 1 , 0 ), + "revealing": ( 0 , 1 , 0 ), + "desperation": ( 1 , 1 , 0 ), + "impatience": ( 0 , 1 , 0 ), + "pacifism": ( 0 , 1 , 0 ), + "influence": ( 0 , 1 , 0 ), + "narcolepsy": ( 0 , 1 , 0 ), + "exchange": ( 0 , 1 , 0 ), + "lycanthropy": ( 0 , 1 , 0 ), + "luck": ( 0 , 1 , 0 ), + "pestilence": ( 1 , 1 , 0 ), + "retribution": ( 4 , 1 , 0 ), + "misdirection": ( 0 , 1 , 0 ), + "deceit": ( 0 , 1 , 0 ), } + + # get default values for wolf shaman's chances + for totem, (s, cs, ws) in self.TOTEM_CHANCES.items(): + self.TOTEM_CHANCES[totem] = (s, cs, var.TOTEM_CHANCES[totem][2]) + self.ROLE_INDEX = ( 8 , 10 , 12 , 13 , 14 , 15 , 17 , 18 , 21 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE.update({ # village roles @@ -620,24 +627,28 @@ class GuardianMode(GameMode): "cursed villager" : ( 1 , 1 , 2 , 2 , 2 ), }) - self.TOTEM_CHANCES = { # shaman , crazed - "death": ( 4 , 1 ), - "protection": ( 8 , 1 ), - "silence": ( 2 , 1 ), - "revealing": ( 0 , 1 ), - "desperation": ( 0 , 1 ), - "impatience": ( 0 , 1 ), - "pacifism": ( 0 , 1 ), - "influence": ( 0 , 1 ), - "narcolepsy": ( 0 , 1 ), - "exchange": ( 0 , 1 ), - "lycanthropy": ( 0 , 1 ), - "luck": ( 3 , 1 ), - "pestilence": ( 0 , 1 ), - "retribution": ( 6 , 1 ), - "misdirection": ( 4 , 1 ), + self.TOTEM_CHANCES = { # shaman , crazed , wolf + "death": ( 4 , 1 , 0 ), + "protection": ( 8 , 1 , 0 ), + "silence": ( 2 , 1 , 0 ), + "revealing": ( 0 , 1 , 0 ), + "desperation": ( 0 , 1 , 0 ), + "impatience": ( 0 , 1 , 0 ), + "pacifism": ( 0 , 1 , 0 ), + "influence": ( 0 , 1 , 0 ), + "narcolepsy": ( 0 , 1 , 0 ), + "exchange": ( 0 , 1 , 0 ), + "lycanthropy": ( 0 , 1 , 0 ), + "luck": ( 3 , 1 , 0 ), + "pestilence": ( 0 , 1 , 0 ), + "retribution": ( 6 , 1 , 0 ), + "misdirection": ( 4 , 1 , 0 ), + "deceit": ( 0 , 1 , 0 ), } + for totem, (s, cs, ws) in self.TOTEM_CHANCES.items(): + self.TOTEM_CHANCES[totem] = (s, cs, var.TOTEM_CHANCES[totem][2]) + def startup(self): events.add_listener("chk_win", self.chk_win) diff --git a/src/settings.py b/src/settings.py index e405c4f..d95dee3 100644 --- a/src/settings.py +++ b/src/settings.py @@ -143,22 +143,23 @@ ALPHA_WOLF_NIGHTS = 3 # alpha wolf turns the target into a wolf after this many DOCTOR_IMMUNIZATION_MULTIPLIER = 0.135 # ceil(num_players * multiplier) = number of immunizations -TOTEM_ORDER = ( "shaman" , "crazed shaman" ) -TOTEM_CHANCES = { "death": ( 1 , 1 ), - "protection": ( 1 , 1 ), - "silence": ( 1 , 1 ), - "revealing": ( 1 , 1 ), - "desperation": ( 1 , 1 ), - "impatience": ( 1 , 1 ), - "pacifism": ( 1 , 1 ), - "influence": ( 1 , 1 ), - "narcolepsy": ( 0 , 1 ), - "exchange": ( 0 , 1 ), - "lycanthropy": ( 0 , 1 ), - "luck": ( 0 , 1 ), - "pestilence": ( 0 , 1 ), - "retribution": ( 0 , 1 ), - "misdirection": ( 0 , 1 ), +TOTEM_ORDER = ( "shaman" , "crazed shaman" , "wolf shaman" ) +TOTEM_CHANCES = { "death": ( 1 , 1 , 0 ), + "protection": ( 1 , 1 , 1 ), + "silence": ( 1 , 1 , 1 ), + "revealing": ( 1 , 1 , 0 ), + "desperation": ( 1 , 1 , 0 ), + "impatience": ( 1 , 1 , 1 ), + "pacifism": ( 1 , 1 , 1 ), + "influence": ( 1 , 1 , 0 ), + "narcolepsy": ( 0 , 1 , 0 ), + "exchange": ( 0 , 1 , 0 ), + "lycanthropy": ( 0 , 1 , 1 ), + "luck": ( 0 , 1 , 1 ), + "pestilence": ( 0 , 1 , 0 ), + "retribution": ( 0 , 1 , 1 ), + "misdirection": ( 0 , 1 , 1 ), + "deceit": ( 0 , 1 , 1 ), } GAME_MODES = {} @@ -192,6 +193,7 @@ ROLE_GUIDE = OrderedDict([ # This is order-sensitive - many parts of the code re ("werecrow" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 )), ("werekitten" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("wolf mystic" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), + ("wolf shaman" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("fallen angel" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("doomsayer" , ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 )), ("wolf cub" , ( 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 )), @@ -248,7 +250,7 @@ ROLE_GUIDE = OrderedDict([ # This is order-sensitive - many parts of the code re # Harlot dies when visiting, seer sees as wolf, gunner kills when shooting, GA and bodyguard have a chance at dying when guarding # If every wolf role dies, and there are no remaining traitors, the game ends and villagers win (monster may steal win) -WOLF_ROLES = frozenset({"wolf", "alpha wolf", "werecrow", "wolf cub", "werekitten", "wolf mystic", "fallen angel", "doomsayer"}) +WOLF_ROLES = frozenset({"wolf", "alpha wolf", "werecrow", "wolf cub", "werekitten", "wolf mystic", "wolf shaman", "fallen angel", "doomsayer"}) # Access to wolfchat, and counted towards the # of wolves vs villagers when determining if a side has won WOLFCHAT_ROLES = WOLF_ROLES | {"traitor", "hag", "sorcerer", "warlock"} # Wins with the wolves, even if the roles are not necessarily wolves themselves diff --git a/src/wolfgame.py b/src/wolfgame.py index dcee775..be66388 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -3534,7 +3534,8 @@ def rename_player(cli, prefix, nick): var.INFLUENTIAL, var.LYCANTHROPES, var.TOBELYCANTHROPES, var.LUCKY, var.TOBELUCKY, var.SICK, var.DISEASED, var.TOBEDISEASED, var.RETRIBUTION, var.MISDIRECTED, var.TOBEMISDIRECTED, var.EXCHANGED, var.IMMUNIZED, var.CURED_LYCANS, var.ALPHA_WOLVES, var.CURSED, var.CHARMERS, - var.CHARMED, var.TOBECHARMED, var.PRIESTS, var.CONSECRATING, var.ENTRANCED_DYING, var.DYING): + var.CHARMED, var.TOBECHARMED, var.PRIESTS, var.CONSECRATING, var.ENTRANCED_DYING, var.DYING, + var.DECEIVED): if prefix in setvar: setvar.remove(prefix) setvar.add(nick) @@ -3929,6 +3930,7 @@ def transition_day(cli, gameid=0): var.TOBEDISEASED = set() var.RETRIBUTION = set() var.MISDIRECTION = set() + var.DECEIVED = set() # Give out totems here for shaman, (victim, target) in var.SHAMANS.items(): @@ -3963,6 +3965,8 @@ def transition_day(cli, gameid=0): var.RETRIBUTION.add(victim) elif totemname == "misdirection": var.TOBEMISDIRECTED.add(victim) + elif totemname == "deceit": + var.DECEIVED.add(victim) else: debuglog("{0} {1}: INVALID TOTEM {2} TO {3}".format(shaman, var.get_role(shaman), totemname, victim)) if target != victim: @@ -4616,7 +4620,7 @@ def chk_nightdone(cli): "sorcerer", "hunter", "hag", "shaman", "crazed shaman", "augur", "werekitten", "warlock", "piper", "wolf mystic", "fallen angel", "dullahan", "vigilante", "doomsayer", "doomsayer", # NOT a mistake, doomsayer MUST be listed twice - "prophet") + "prophet", "wolf shaman", "wolf shaman") # wolf shaman also must be listed twice for ghost, against in var.VENGEFUL_GHOSTS.items(): if not against.startswith("!"): @@ -5799,12 +5803,27 @@ def see(cli, nick, chan, rest): victimrole = var.DEFAULT_ROLE if var.DEFAULT_SEEN_AS_VILL: victimrole = "villager" + if victim in var.DECEIVED: + if victimrole == "wolf": + victimrole = "villager" + else: + victimrole = "wolf" + + if nick in var.DECEIVED: # it DOES stack! so if both victim and seer have the totem, it's canceled out + if victimrole == "wolf": + victimrole = "villager" + else: + victimrole = "wolf" + pm(cli, nick, (messages["seer_success"]).format(victim, victimrole)) debuglog("{0} ({1}) SEE: {2} ({3}) as {4}".format(nick, role, victim, vrole, victimrole)) elif role == "oracle": iswolf = False if (victimrole in var.SEEN_WOLF and victimrole not in var.SEEN_DEFAULT) or victim in var.ROLES["cursed villager"]: iswolf = True + # deceit totem acts on both target and actor, so if both have them, they cancel each other out + if (victim in var.DECEIVED) ^ (nick in var.DECEIVED): + iswolf = not iswolf pm(cli, nick, (messages["oracle_success"]).format(victim, "" if iswolf else "\u0002not\u0002 ", "\u0002" if iswolf else "")) debuglog("{0} ({1}) SEE: {2} ({3}) (Wolf: {4})".format(nick, role, victim, vrole, str(iswolf))) elif role == "augur": @@ -5919,7 +5938,6 @@ def immunize(cli, nick, chan, rest): def get_bitten_message(nick): time_left = var.BITTEN[nick] role = var.get_role(nick) - message = "" if role == "guardian angel": if time_left <= 1: message = messages["angel_bit_1"] @@ -5934,6 +5952,13 @@ def get_bitten_message(nick): message = messages["seer_bit_2"] else: message = messages["seer_bit_3"] + elif role in var.TOTEM_ORDER and role != "wolf shaman": + if time_left <= 1: + message = messages["shaman_bit_1"] + elif time_left == 2: + message = messages["shaman_bit_2"] + else: + message = messages["shaman_bit_3"] else: if time_left <= 1: message = messages["villager_bit_1"] @@ -6578,6 +6603,10 @@ def transition_night(cli): pm(cli, chump, messages["seer_turn"]) newrole = "doomsayer" debuglog("{0} ({1}) TURNED DOOMSAYER".format(chump, chumprole)) + elif chumprole in var.TOTEM_ORDER: + pm(cli, chump, messages["shaman_turn"]) + newrole = "wolf shaman" + debuglog("{0} ({1}) TURNED WOLF SHAMAN".format(chump, chumprole)) else: pm(cli, chump, messages["bitten_turn"]) debuglog("{0} ({1}) TURNED WOLF".format(chump, chumprole)) @@ -6662,6 +6691,8 @@ def transition_night(cli): pm(cli, wolf, messages["warlock_notify"].format(cursed)) elif role == "wolf mystic": pm(cli, wolf, messages["wolf_mystic_notify"]) + elif role == "wolf shaman": + pm(cli, wolf, messages["wolf_shaman_notify"]) elif role == "fallen angel": pm(cli, wolf, messages["fallen_angel_notify"]) elif role == "doomsayer": @@ -6838,7 +6869,8 @@ def transition_night(cli): var.TOTEMS[shaman] = t break if shaman in var.PLAYERS and not is_user_simple(shaman): - pm(cli, shaman, messages["shaman_notify"].format(role, "random " if shaman in var.ROLES["crazed shaman"] else "")) + if role not in var.WOLFCHAT_ROLES: + pm(cli, shaman, messages["shaman_notify"].format(role, "random " if shaman in var.ROLES["crazed shaman"] else "")) if role != "crazed shaman": totem = var.TOTEMS[shaman] tmsg = messages["shaman_totem"].format(totem) @@ -6872,14 +6904,18 @@ def transition_night(cli): tmsg += messages["retribution_totem"] elif totem == "misdirection": tmsg += messages["misdirection_totem"] + elif totem == "deceit": + tmsg += messages["deceit_totem"] else: tmsg += messages["generic_bug_totem"] pm(cli, shaman, tmsg) else: - pm(cli, shaman, messages["shaman_simple"].format(role)) + if role not in var.WOLFCHAT_ROLES: + pm(cli, shaman, messages["shaman_simple"].format(role)) if role != "crazed shaman": pm(cli, shaman, messages["totem_simple"].format(var.TOTEMS[shaman])) - pm(cli, shaman, "Players: " + ", ".join(pl)) + if role not in var.WOLFCHAT_ROLES: + pm(cli, shaman, "Players: " + ", ".join(pl)) for hunter in var.ROLES["hunter"]: if hunter in var.HUNTERS: @@ -7412,6 +7448,7 @@ def start(cli, nick, chan, forced = False, restart = ""): var.PRAYED = {} var.SICK = set() var.DULLAHAN_TARGETS = {} + var.DECEIVED = set() var.DEADCHAT_PLAYERS = set() var.SPECTATING_WOLFCHAT = set()