Skip to content

Commit

Permalink
pb refinements
Browse files Browse the repository at this point in the history
- Remove access to the vigilante !kill command again. Instead, when a
  vigilante visits the house of a wolf/vampire they have hard evidence
  for, they are now guaranteed to kill that target (regardless of
  whether or not the target is home).
- Players can no longer visit the same "type" of location two nights in
  a row (someone else's house, forest, square), with the following
  exceptions:
  1. Everyone can stay home multiple nights in a row
  2. Wolves can hunt in the forest multiple nights in a row
  3. Vampires can hunt in the square multiple nights in a row

This should lead to more dynamic gameplay and more reliable hunting for
wolves/vampires.
  • Loading branch information
skizzerz committed Aug 31, 2024
1 parent c4d243f commit 4bf59a9
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 24 deletions.
3 changes: 3 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,9 @@
"pactbreaker_wolf_win": "Game over! The village has dealt with the vampires and ousted the vigilante, allowing them to re-establish their protection pact with the wolves.",
"pactbreaker_vampire_win": "Game over! The vampires have overrun the village, making them the new masters of the town.",
"pactbreaker_no_visit": "You are locked in the stocks tonight and unable to visit other locations.",
"pactbreaker_no_visit_twice_forest": "You may not visit the forest two nights in a row.",
"pactbreaker_no_visit_twice_square": "You may not visit the village square two nights in a row.",
"pactbreaker_no_visit_twice_house": "You may not visit someone else's house two nights in a row.",
"pactbreaker_wolf_notify": "You are {=wolf!role:article} {=wolf!role:bold}. It is your job to eliminate the {=vampire!role:plural} and {=vigilante!role:plural} to re-establish your pact with the village.\n{=pactbreaker_notify!message}",
"pactbreaker_vampire_notify": "You are {=vampire!role:article} {=vampire!role:bold}. It is your job to kill all the villagers.\n{=pactbreaker_notify!message}",
"pactbreaker_vigilante_notify": "You are {=vigilante!role:article} {=vigilante!role:bold}. It is your job to eliminate the {=wolf!role:plural} and {=vampire!role:plural} to end their threat over the village.\n{=pactbreaker_notify!message}",
Expand Down
52 changes: 28 additions & 24 deletions src/gamemodes/pactbreaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from src.roles.helper.wolves import send_wolfchat_message, wolf_kill, wolf_retract
from src.roles.vampire import send_vampire_chat_message, vampire_bite, vampire_retract
from src.roles.vampire import on_player_protected as vampire_drained
from src.roles.vigilante import vigilante_retract, vigilante_pass, vigilante_kill, KILLS as vigilante_kills
from src.roles.vigilante import vigilante_retract, vigilante_pass, vigilante_kill

@game_mode("pactbreaker", minp=6, maxp=24, likelihood=0)
class PactBreakerMode(GameMode):
Expand Down Expand Up @@ -74,6 +74,7 @@ def __init__(self, arg=""):
# messages to send on night kills; only populated during transition_day so user containers are unnecessary
self.night_kill_messages: dict[tuple[User, User], Optional[Location]] = {}
self.visiting: UserDict[User, Location] = UserDict()
self.prev_visiting: UserDict[User, Location] = UserDict()
self.drained = UserSet()
self.voted: DefaultUserDict[User, int] = DefaultUserDict(int)
self.last_voted: Optional[User] = None
Expand All @@ -86,7 +87,6 @@ def __init__(self, arg=""):
kwargs = dict(chan=False, pm=True, playing=True, phases=("night",), register=False)
self.pass_command = command("pass", **kwargs)(self.stay_home)
self.visit_command = command("visit", **kwargs)(self.visit)
self.kill_command = command("kill", roles=("vigilante",), **kwargs)

def startup(self):
super().startup()
Expand All @@ -96,6 +96,7 @@ def startup(self):
self.drained.clear()
self.collected_evidence.clear()
self.visiting.clear()
self.prev_visiting.clear()
# register !visit and !pass, remove all role commands
self.visit_command.register()
self.pass_command.register()
Expand Down Expand Up @@ -159,7 +160,6 @@ def on_chk_nightdone(self, evt: Event, var: GameState):
evt.data["acted"].clear()
evt.data["nightroles"].clear()
evt.data["acted"].extend(self.visiting)
evt.data["acted"].extend(vigilante_kills)
evt.data["nightroles"].extend(self.active_players)
evt.stop_processing = True

Expand Down Expand Up @@ -370,16 +370,13 @@ def on_night_kills(self, evt: Event, var: GameState):
cards = deck[i:i+draws]
i += draws
role = get_main_role(var, visitor)
vigilante_luck = random.random() < 0.4
if ((vigilante_luck or is_home) and role == "vigilante"
and owner in self.collected_evidence[visitor] and owner_role in ("wolf", "vampire")):
# vigilantes will murder a known wolf or vampire if they're home
# (and sometimes even if they're not)
have_evidence = owner in self.collected_evidence[visitor]
if role == "vigilante" and have_evidence and owner_role in ("wolf", "vampire"):
# vigilantes will murder a known wolf or vampire by visiting their house
evt.data["victims"].add(owner)
evt.data["killers"][owner].append(visitor)
self.night_kill_messages[(visitor, owner)] = location
elif (not is_home and owner in self.collected_evidence[visitor]
and owner_role == "vampire" and role != "vampire"):
elif not is_home and have_evidence and owner_role == "vampire" and role != "vampire":
# non-vampires destroy known vampires that aren't home
evt.data["victims"].add(owner)
evt.data["killers"][owner].append(visitor)
Expand Down Expand Up @@ -499,6 +496,8 @@ def on_begin_day(self, evt: Event, var: GameState):
# every player is active again (stocks only lasts for one night)
self.active_players.clear()
self.active_players.update(get_players(var))
self.prev_visiting.clear()
self.prev_visiting.update(self.visiting)
self.visiting.clear()
# if someone was locked up last night, ensure they can't be locked up again tonight
if self.last_voted is not None:
Expand Down Expand Up @@ -565,21 +564,13 @@ def stay_home(self, wrapper: MessageDispatcher, message: str):
self.visiting[wrapper.source] = get_home(wrapper.source.game_state, wrapper.source)
wrapper.pm(messages["no_visit"])

def kill(self, wrapper: MessageDispatcher, message: str):
"""Kill someone at night, but you die too if they aren't a wolf or vampire!"""
del self.visiting[:wrapper.source:]
vigilante_kill.caller(wrapper, message)

def visit(self, wrapper: MessageDispatcher, message: str):
"""Visit a location to collect evidence."""
var = wrapper.game_state
if wrapper.source not in self.active_players:
wrapper.pm(messages["pactbreaker_no_visit"])
return

if wrapper.source in get_players(var, ("vigilante",)):
del vigilante_kills[:wrapper.source:]

prefix = re.split(" +", message)[0]
aliases = {
"forest": messages.raw("_commands", "forest"),
Expand All @@ -605,19 +596,32 @@ def visit(self, wrapper: MessageDispatcher, message: str):
target_location = get_home(var, target_player)
is_special = False

# Don't let a person visit the same location multiple nights in a row, with the following exceptions:
# 1. Everyone can stay home multiple nights in a row
# 2. Wolves can hunt in the forest multiple nights in a row
# 3. Vampires can hunt in the square multiple nights in a row
prev_location = self.prev_visiting.get(wrapper.source)
player_home = get_home(var, wrapper.source)
player_role = get_main_role(var, wrapper.source)
prev_name = prev_location.name if prev_location in (Forest, VillageSquare) else "house"
target_name = target_location.name if target_location in (Forest, VillageSquare) else "house"
if prev_name == target_name and prev_location is not player_home:
if target_location is Forest and player_role in Wolf:
pass
elif target_location is VillageSquare and player_role in Vampire:
pass
else:
wrapper.pm(messages["pactbreaker_no_visit_twice_{0}".format(target_name)])
return

self.visiting[wrapper.source] = target_location
if is_special:
wrapper.pm(messages["pactbreaker_visiting_{0}".format(target_location.name)])
else:
wrapper.pm(messages["pactbreaker_visiting_house"].format(target_player))

# relay to wolfchat/vampire chat as appropriate
if is_special:
relay_key = "pactbreaker_relay_visit_{0}".format(target_location.name)
else:
relay_key = "pactbreaker_relay_visit_house"

player_role = get_main_role(var, wrapper.source)
relay_key = "pactbreaker_relay_visit_{0}".format(target_name)
if player_role in Wolf:
# command is "kill" so that this is relayed even if gameplay.wolfchat.only_kill_command is true
send_wolfchat_message(var,
Expand Down

0 comments on commit 4bf59a9

Please sign in to comment.