Skip to content

Commit

Permalink
Pactbreaker v3
Browse files Browse the repository at this point in the history
- Vampires staying home can bite people visiting them
- Overhaul house evidence deck to make it almost impossible to collect
  evidence on a non-villager in the first visit to their house, but to
  guarantee evidence by the 2nd visit (if visiting together with
  someone) or 3rd visit (if alone).
- Killing roles (wolf/vamp/vig) visiting square now prioritize the
  person in the stocks when drawing an evidence card rather than
  participating in evidence sharing:
  - Wolves kill the person in the stocks unless they are another wolf or
    are known to be a villager
  - Vampires always fully drain the person in the stocks
  - Vigilantes kill known wolves/vampires in the stocks, and otherwise
    gain evidence on the person
- Overhaul the forest location
  - All visitors (including wolves) now draw from the forest deck
  - The forest deck has far fewer empty-handed cards
  - When a non-vampire receives a wolf list, it will now be 3+ players
    instead of 4+ players
  - Wolves can sometimes share evidence with each other when visiting
    the forest
  - Vampires can drain people in the forest
  - Visitors to the forest risk exposing evidence on themselves. Usually
    this is reciprocal where two people meeting in the forest exchange
    roles. However, wolves and vigilantes will lie and pretend to be
    villagers.
  • Loading branch information
skizzerz committed Oct 2, 2024
1 parent 88f7a9b commit 215a0f1
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 38 deletions.
3 changes: 3 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,9 @@
"pactbreaker_house_empty_2": "{0:@} seems to have stayed home tonight, so you remain hidden outside looking through an open window. However, you were unable to discover any useful evidence through your observations.",
"pactbreaker_forest_evidence": "You discover some items in the forest leading you to believe that at least one of these people is {=wolf!role:article} {=wolf!role:bold}: {0:join}",
"pactbreaker_forest_evidence_single": "You discover some items in the forest leading you to believe that {0:@} is {=wolf!role:article} {=wolf!role:bold}!",
"pactbreaker_forest_evidence_nonwolf": "You find {0:@} stalking around the forest as well. After a quick exchange of pleasantries, you learn that they are {1!role:article} {1!role:bold} before continuing on you way.",
"pactbreaker_forest_share_nothing": "You compared notes with the other wolves you met in the forest last night, but did not learn of any new information.",
"pactbreaker_forest_share": "You compared notes with the other wolves you met in the forest last night, and learned the following: {0:join}",
"pactbreaker_vote": [
"The village has decided to lock {0:@} into the stocks until tomorrow."
],
Expand Down
147 changes: 109 additions & 38 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
from collections import defaultdict
from typing import Iterable, Optional

from Tools.scripts.findlinksto import visit

from src import users, channels
from src.users import User, FakeUser
from src.containers import UserSet, UserDict, DefaultUserDict
Expand Down Expand Up @@ -192,42 +194,100 @@ def on_night_kills(self, evt: Event, var: GameState):

if location is Forest:
wolves = get_players(var, ("wolf",))
non_wolves = [x for x in visitors if x not in wolves]
deck = []
evidence_sharing = set()
for wolf in wolves:
deck.append(("evidence", wolf))
deck.append(("evidence", wolf))
if wolf in visitors:
deck.append(("hunted", wolf))
deck.append(("hunted", wolf))
while len(deck) < max(8, len(non_wolves)):
for visitor in visitors:
if visitor in wolves:
deck.append(("hunted", visitor))
else:
deck.append(("evidence", visitor))
while len(deck) < max(5, len(visitors)):
deck.append(("empty-handed", None))

random.shuffle(deck)
for i, visitor in enumerate(non_wolves):
role = get_main_role(var, visitor)
card, wolf = deck[i]
if card == "evidence":
wolf_list = [wolf]
choices = [x for x in get_players(var) if x not in (wolf, visitor)]
if role != "vampire" and len(choices) >= 4:
wolf_list.extend(random.sample(choices, int(len(choices) / 4) + 2))
# give a list of potential wolves (at least one of which is wolf)
for i, visitor in enumerate(visitors):
card, target = deck[i]
visitor_role = get_main_role(var, visitor)
target_role = get_main_role(var, target)
if target is visitor:
visitor.send(messages["pactbreaker_forest_empty"])
elif card == "evidence" and target_role == "wolf" and visitor_role == "wolf":
if target in visitors:
evidence_sharing.add(target)
evidence_sharing.add(visitor)
else:
visitor.send(messages["pactbreaker_forest_empty"])
elif card == "evidence" and target_role == "wolf":
wolf_list = [target]
choices = [x for x in get_players(var) if x not in (target, visitor)]
if visitor_role != "vampire" and len(choices) >= 4:
wolf_list.extend(random.sample(choices, int(len(choices) / 4) + 1))
# give a list of 3+ potential wolves (at least one of which is wolf)
if len(wolf_list) == 1:
visitor.send(messages["pactbreaker_forest_evidence_single"].format(wolf))
self.collected_evidence[visitor].add(wolf)
visitor.send(messages["pactbreaker_forest_evidence_single"].format(target))
self.collected_evidence[visitor].add(target)
else:
visitor.send(messages["pactbreaker_forest_evidence"].format(wolf_list))
elif card == "hunted" and role == "vampire":
self.collected_evidence[wolf].add(visitor)
wolf.send(messages["pactbreaker_hunter_vampire"].format(visitor))
elif card == "evidence" and (visitor_role == "vampire" or target_role == "vampire"):
if visitor_role == "wolf":
# treat as a hunted card instead (but with roles flipped since target is the vampire)
self.collected_evidence[visitor].add(target)
visitor.send(messages["pactbreaker_hunter_vampire"].format(target))
target.send(messages["pactbreaker_hunted_vampire"])
elif visitor_role == "vampire" and target_role == "vampire":
visitor.send(messages["pactbreaker_forest_empty"])
else:
vampire = visitor if visitor_role == "vampire" else target
victim = target if visitor_role == "vampire" else visitor
evt.data["victims"].add(victim)
evt.data["killers"][victim].append(vampire)
evt.data["kill_priorities"][vampire] = 10
self.night_kill_messages[(vampire, victim)] = location
elif card == "evidence":
self.collected_evidence[visitor].add(target)
if visitor_role == "villager":
self.collected_evidence[target].add(visitor)
# wolves and vigilante visitors hide their true nature, but a vigilante target is caught off-guard
# the villager lie only applies to wolves/vigilantes because vampire was handled in the above case
visitor.send(messages["pactbreaker_forest_evidence_nonwolf"].format(target, target_role))
target.send(messages["pactbreaker_forest_evidence_nonwolf"].format(visitor, "villager"))
elif card == "hunted" and visitor_role == "wolf":
evidence_sharing.add(visitor)
evidence_sharing.add(target)
elif card == "hunted" and visitor_role == "vampire":
self.collected_evidence[target].add(visitor)
target.send(messages["pactbreaker_hunter_vampire"].format(visitor))
visitor.send(messages["pactbreaker_hunted_vampire"])
elif card == "hunted" and (role != "villager" or visitor not in self.collected_evidence[wolf]):
elif card == "hunted" and (visitor_role != "villager" or visitor not in self.collected_evidence[target]):
evt.data["victims"].add(visitor)
evt.data["killers"][visitor].append(wolf)
self.night_kill_messages[(wolf, visitor)] = location
evt.data["killers"][visitor].append(target)
self.night_kill_messages[(target, visitor)] = location
else:
visitor.send(messages["pactbreaker_forest_empty"])

# calculate shared evidence
shared_evidence = set()
ws = set(wolves)
for wolf in evidence_sharing:
# extra set() wrapper to make PyCharm infer the types correctly
shared_evidence.update(set(self.collected_evidence[wolf]))
for wolf in evidence_sharing:
if not shared_evidence:
# nobody has evidence to share, so everyone treats this as empty-handed instead
wolf.send(messages["pactbreaker_forest_empty"])
continue
if shared_evidence - self.collected_evidence[wolf] - ws:
entries = []
for target in shared_evidence - self.collected_evidence[wolf] - ws:
entries.append(messages["players_list_entry"].format(target, "",
(get_main_role(var, target),)))
wolf.send(messages["pactbreaker_forest_share"].format(entries))
self.collected_evidence[wolf].update(shared_evidence - ws)
else:
wolf.send(messages["pactbreaker_forest_share_nothing"])
elif location is VillageSquare:
deck = [("empty-handed", None)]
# figure out who is in the stocks (if anyone)
Expand Down Expand Up @@ -256,10 +316,15 @@ def on_night_kills(self, evt: Event, var: GameState):
for i, visitor in enumerate(visitors):
role = get_main_role(var, visitor)
card, actor = deck[i]
# killing roles with evidence on the person in the stocks treat drawing an evidence
# wolves with evidence on the person in the stocks treat drawing an evidence
# card as drawing the card that lets them kill the person in the stocks instead
evidence_special = card == "evidence" and target in self.collected_evidence[visitor]
if role == "wolf" and (card == "hunted" or evidence_special):
wolf_special = card == "evidence" and target in self.collected_evidence[visitor]
# vampires always kill non-vampires in the stocks if they draw an evidence card
vamp_special = card == "evidence" and target is not None and target_role != "vampire"
# vigilantes gain evidence on the person in the stocks on drawing an evidence card,
# or kills wolves/vampires if they already have evidence on them
vig_special = card == "evidence" and target is not None and (target not in self.collected_evidence[visitor] or target_role not in ("villager", "vigilante"))
if role == "wolf" and (card == "hunted" or wolf_special):
# wolves kill the player in the stocks (even vampires)
if not target or target_role == "wolf":
# but don't kill other wolves
Expand All @@ -268,7 +333,7 @@ def on_night_kills(self, evt: Event, var: GameState):
evt.data["victims"].add(target)
evt.data["killers"][target].append(visitor)
self.night_kill_messages[(visitor, target)] = location
elif role == "vampire" and (card == "drained" or evidence_special):
elif role == "vampire" and (card == "drained" or vamp_special):
# vampires fully drain the player in the stocks
if not target or target_role == "vampire":
# but don't drain other vampires
Expand All @@ -278,13 +343,13 @@ def on_night_kills(self, evt: Event, var: GameState):
evt.data["killers"][target].append(visitor)
evt.data["kill_priorities"][actor] = 10
self.night_kill_messages[(visitor, target)] = location
elif role == "vigilante" and (card == "exposed" or evidence_special):
elif role == "vigilante" and (card == "exposed" or vig_special):
# vigilantes kill the player in the stocks if they have hard evidence on them,
# otherwise they gain hard evidence
if not target:
# nobody in the stocks
visitor.send(messages["pactbreaker_square_empty"])
elif target in self.collected_evidence[visitor]:
elif target in self.collected_evidence[visitor] and target_role not in ("villager", "vigilante"):
evt.data["victims"].add(target)
evt.data["killers"][target].append(visitor)
self.night_kill_messages[(visitor, target)] = location
Expand Down Expand Up @@ -315,7 +380,7 @@ def on_night_kills(self, evt: Event, var: GameState):
self.collected_evidence[visitor].add(actor)
visitor.send(messages["pactbreaker_exposed"].format(actor))
elif card == "evidence":
# share evidence with every other player who has drawn an evidence card
# share evidence with everyone else who has drawn an evidence card without triggering special behavior
evidence_sharing.append(visitor)
else:
visitor.send(messages["pactbreaker_square_empty"])
Expand Down Expand Up @@ -353,22 +418,28 @@ def on_night_kills(self, evt: Event, var: GameState):
total_draws = 0
for visitor in visitors:
self.visit_count[visitor][owner] += 1
total_draws += self.visit_count[visitor][owner]
total_draws += (self.visit_count[visitor][owner] - 1) * 2 + 1

owner_role = get_main_role(var, owner)
vampires = [x for x in visitors if get_main_role(var, x) == "vampire"]
num_vampires = len(vampires)
owner_role = get_main_role(var, owner)
if is_home and owner_role == "vampire":
vampires.append(owner)
num_vampires += 1

deck = ["empty-handed",
"empty-handed" if owner_role != "wolf" else "evidence",
"empty-handed" if owner_role != "villager" or is_home else "evidence",
"empty-handed" if is_home else "evidence"]
if total_draws > 4:
for i in range(total_draws - 4):
"empty-handed",
"empty-handed" if owner_role != "villager" or is_home else "evidence"]
if total_draws > 3:
for i in range(0, total_draws - 3, 2):
deck.append("evidence")
deck.append("empty-handed")
if is_home:
deck.append("empty-handed")
random.shuffle(deck)
i = 0
for visitor in visitors:
draws = self.visit_count[visitor][owner]
draws = (self.visit_count[visitor][owner] - 1 * 2) + 1
cards = deck[i:i+draws]
i += draws
role = get_main_role(var, visitor)
Expand Down Expand Up @@ -401,7 +472,7 @@ def on_night_kills(self, evt: Event, var: GameState):
else:
visitor.send(messages["pactbreaker_house_empty_1"].format(owner))

if not is_home and num_vampires > 0 and num_vampires >= num_visitors / 2:
if (not is_home or owner_role == "vampire") and num_vampires > 0 and num_vampires >= num_visitors / 2:
# vampires outnumber non-vampires; drain the non-vampires
i = 0
for visitor in visitors:
Expand Down

0 comments on commit 215a0f1

Please sign in to comment.