diff --git a/NEWS b/NEWS index 4b5da4e..deff39d 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ In this alpha10 release, we have added: * German translation for Eval command help. * German translation for FML command help. * German translation for Greet command help. +* Werewolf module for the Werewolf party game. Bug fixes: diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 408f3f6..214cf14 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -3,6 +3,7 @@ Auto IRC Bot 3.0: Change Log 3.0 Indev =============================================================================== + * Added an epic, Werewolf module. * Made slog() saner. * Syntax for mod_init() changed. Package name is no longer required. * Added German translation to Greet help hash. - nridley diff --git a/modules/Werewolf.pm b/modules/Werewolf.pm new file mode 100644 index 0000000..281ca73 --- /dev/null +++ b/modules/Werewolf.pm @@ -0,0 +1,2036 @@ +# Module: Werewolf. See below for documentation. +# Copyright (C) 2010-2011 Xelhua Development Group, et al. +# This program is free software; rights to this code are stated in doc/LICENSE. +package M::Werewolf; +use strict; +use warnings; +use feature qw(switch); +use API::Std qw(cmd_add cmd_del trans hook_add hook_del timer_add timer_del trans conf_get has_priv match_user); +use API::IRC qw(privmsg notice cmode); +my ($GAME, $PGAME, $GAMECHAN, $GAMETIME, %PLAYERS, %NICKS, @STATIC, $PHASE, $SEEN, $VISIT, $GUARD, %KILL, %WKILL, %LYNCH, %BED, $LVOTEN, $SHOT, $DETECTED); +our $WAIT; +my $FCHAR = (conf_get('fantasy_pf'))[0][0]; + +# Initialization subroutine. +sub _init { + # Create the WOLF command. + cmd_add('WOLF', 2, 0, \%M::Werewolf::HELP_WOLF, \&M::Werewolf::cmd_wolf) or return; + + # Create the on_cprivmsg hook. + hook_add('on_cprivmsg', 'werewolf.updatedata.bed', \&M::Werewolf::on_cprivmsg) or return; + # Create the on_uprivmsg hook. + hook_add('on_uprivmsg', 'werewolf.relay', \&M::Werewolf::on_uprivmsg) or return; + # Create the on_nick hook. + hook_add('on_nick', 'werewolf.updatedata.nick', \&M::Werewolf::on_nick) or return; + # Create the on_part hook. + hook_add('on_part', 'werewolf.updatedata.part', \&M::Werewolf::on_part) or return; + # Create the on_kick hook. + hook_add('on_kick', 'werewolf.updatedata.kick', \&M::Werewolf::on_kick) or return; + # Create the on_quit hook. + hook_add('on_quit', 'werewolf.updatedata.quit', \&M::Werewolf::on_quit) or return; + # Create the on_rehash hook. + hook_add('on_rehash', 'werewolf.rehash', \&M::Werewolf::on_rehash) or return; + + # Success. + return 1; +} + +# Void subroutine. +sub _void { + # Delete the WOLF command. + cmd_del('WOLF') or return; + + # Delete the on_cprivmsg hook. + hook_del('on_cprivmsg', 'werewolf.updatedata.bed') or return; + # Delete the on_uprivmsg hook. + hook_del('on_uprivmsg', 'werewolf.relay') or return; + # Delete the on_nick hook. + hook_del('on_nick', 'werewolf.updatedata.nick') or return; + # Delete the on_part hook. + hook_del('on_part', 'werewolf.updatedata.part') or return; + # Delete the on_kick hook. + hook_del('on_kick', 'werewolf.updatedata.kick') or return; + # Delete the on_quit hook. + hook_del('on_quit', 'werewolf.updatedata.quit') or return; + # Delete the on_rehash hook. + hook_del('on_rehash', 'werewolf.rehash') or return; + + # Success. + return 1; +} + +# Commands hash. +my %COMMANDS = ( + 'join' => 'wolf join', + 'begin' => 'wolf begin', + 'see' => 'wolf see ', + 'visit' => 'wolf visit ', + 'guard' => 'wolf guard ', + 'kill' => 'wolf kill ', + 'lynch' => 'wolf lynch ', + 'shoot' => 'wolf shoot ', + 'id' => 'wolf id ', +); + +# Help hash for the WOLF command. +our %HELP_WOLF = ( + en => "This command allows you to perform various actions in a game of Werewolf (A.K.A. Mafia). \2Syntax:\2 WOLF (START|JOIN|BEGIN|LYNCH|RETRACT|SHOOT|QUIT|KICK|VOTES|STATS / SEE|ID|VISIT|GUARD|KILL) [parameters]", +); + +# Callback for the WOLF command. +sub cmd_wolf { + my ($src, @argv) = @_; + + # We require at least one parameter. + if (!defined $argv[0]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if this was a private or public message. + if (exists $src->{chan}) { + # Iterate the parameter. + given (uc $argv[0]) { + when ('START') { + # WOLF START + + # Check if this is the game channel. + if (conf_get('werewolf:chan')) { + if ($src->{svr}.'/'.$src->{chan} ne (conf_get('werewolf:chan'))[0][0]) { + notice($src->{svr}, $src->{nick}, "This is not a valid Werewolf channel. Please join \2".(conf_get('werewolf:chan'))[0][0]."\2 instead."); + return; + } + } + + # Make sure a game is not already running. + if ($GAME or $PGAME) { + notice($src->{svr}, $src->{nick}, "A game is already running in \2$GAMECHAN\2."); + return; + } + + # Set variables. + $PGAME = 1; + $GAME = 0; + $GAMECHAN = $src->{svr}.'/'.$src->{chan}; + $PLAYERS{lc $src->{nick}} = 0; + $NICKS{lc $src->{nick}} = $src->{nick}; + $GAMETIME = time; + + # Set waiting time. + $WAIT = 1; + timer_add('werewolf.join_wait', 1, 60, sub { $M::Werewolf::WAIT = 0 }); + + # Game started. + cmode($src->{svr}, $src->{chan}, "+v $src->{nick}"); + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 has started a game of Werewolf. Type \"".$FCHAR.$COMMANDS{'join'}.'" to join. Type "'.$FCHAR.$COMMANDS{begin}.'" to begin the game.'); + } + when (/^(JOIN|J)$/) { + # WOLF JOIN + + # Check if a game is running. + if (!$PGAME) { + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + else { + notice($src->{svr}, $src->{nick}, 'A game is already in play, try again next time.'); + return; + } + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're not already playing. + if (defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re already playing!'); + return; + } + + # Maximum amount of players is 30. + if (keys %PLAYERS >= 30) { + notice($src->{svr}, $src->{nick}, 'Too many players! Try again next time.'); + return; + } + + # Set variables. + $PLAYERS{lc $src->{nick}} = 0; + $NICKS{lc $src->{nick}} = $src->{nick}; + + # Send message. + my ($gsvr, $gchan) = split '/', $GAMECHAN; + cmode($gsvr, $gchan, "+v $src->{nick}"); + privmsg($gsvr, $gchan, "\2$src->{nick}\2 joined the game."); + } + when (/^(BEGIN|B)$/) { + # WOLF BEGIN + + # Check if a game is running. + if (!$PGAME) { + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + else { + notice($src->{svr}, $src->{nick}, 'Werewolf is already in play.'); + return; + } + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check join timeout. + if ($WAIT and keys %PLAYERS != 30) { + my $time = time - $GAMETIME; + $time = 60 - $time; + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: Please wait at least \2$time\2 more seconds."); + return; + } + + # Need four or more players. + if (keys %PLAYERS < 4) { + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: Four or more players are required to play."); + return; + } + + # First, determine how many players to declare a wolf. + my $cwolves; + if (keys %PLAYERS >= 4) { $cwolves++ } + if (keys %PLAYERS >= 9) { $cwolves++ } + if (keys %PLAYERS >= 15) { $cwolves++ } + if (keys %PLAYERS >= 21) { $cwolves++ } + if (keys %PLAYERS >= 25) { $cwolves++ } + if (keys %PLAYERS == 30) { $cwolves++ } + # Only one seer, harlot, guardian angel, traitor and detective. + my $cseers = 1; + my $charlots = my $cangels = my $ctraitors = my $cdetectives = 0; + if (keys %PLAYERS >= 6) { $charlots++ unless conf_get('werewolf:rated-g') } + if (keys %PLAYERS >= 9) { $cangels++ unless conf_get('werewolf:no-angels') } + if (keys %PLAYERS >= 12 and conf_get('werewolf:traitors')) { $ctraitors++ } + if (keys %PLAYERS >= 16 and conf_get('werewolf:detectives')) { $cdetectives++ } + + # Give all players a role. + while ((my $plyr, undef) = each %PLAYERS) { $PLAYERS{$plyr} = 'v' } + # Push players into a temporary array. + my @plyrs = keys %PLAYERS; + + # Set wolves. + while ($cwolves > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(s|g|h|d|t)$/xsm) { + $PLAYERS{$rpi} = 'w'; + $cwolves--; + $STATIC[0] .= ", \2$NICKS{$rpi}\2"; + } + } + $STATIC[0] = substr $STATIC[0], 2; + # Set seers. + while ($cseers > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(w|g|h|d|t)$/xsm) { + $PLAYERS{$rpi} = 's'; + $cseers--; + $STATIC[1] = "\2$NICKS{$rpi}\2"; + } + } + # Set harlots. + while ($charlots > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(w|g|s|d|t)$/xsm) { + $PLAYERS{$rpi} = 'h'; + $charlots--; + $STATIC[2] = "\2$NICKS{$rpi}\2"; + } + } + # Set guardian angels. + while ($cangels > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(w|h|s|d|t)$/xsm) { + $PLAYERS{$rpi} = 'g'; + $cangels--; + $STATIC[3] = "\2$NICKS{$rpi}\2"; + } + } + # Set traitors. + while ($ctraitors > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(w|h|s|d|g)$/xsm) { + $PLAYERS{$rpi} = 't'; + $ctraitors--; + $STATIC[4] = "\2$NICKS{$rpi}\2"; + } + } + # Set detectives. + while ($cdetectives > 0) { + my $rpi = $plyrs[int rand scalar @plyrs]; + if ($PLAYERS{$rpi} !~ m/^(w|h|s|g|t)$/xsm) { + $PLAYERS{$rpi} = 'd'; + $cdetectives--; + $STATIC[5] = "\2$NICKS{$rpi}\2"; + } + } + + # If there's 6 or more players, give one of them a gun. + if (keys %PLAYERS >= 6) { + my $rpi = $plyrs[int rand scalar @plyrs]; + while ($PLAYERS{$rpi} =~ m/w/xsm || $PLAYERS{$rpi} =~ m/t/xsm) { $rpi = $plyrs[int rand scalar @plyrs] } + $PLAYERS{$rpi} .= 'b'; + } + + # Set variables. + $GAME = 1; + $PGAME = 0; + + # Set bed variables. + foreach (keys %PLAYERS) { $BED{$_} = 0 } + + # All players have their role, so lets begin the game! + my ($gsvr, $gchan) = split '/', $GAMECHAN; + privmsg($gsvr, $gchan, 'Game is now starting.'); + cmode($gsvr, $gchan, '+m'); + # Initialize the nighttime. + _init_night(); + } + when (/^(LYNCH|L)$/) { + # WOLF LYNCH + + # Check if a game is running. + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Only allow lynching during the day. + if ($PHASE ne 'd') { + notice($src->{svr}, $src->{nick}, 'Lynching is only allowed during the day. Please wait patiently for morning.'); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if they've been shot today. + if ($SHOT eq lc $src->{nick}) { + notice($src->{svr}, $src->{nick}, 'You\'re wounded and resting, thus you are unable to vote for the day.'); + return; + } + + # Check if the target is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + notice($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # If they've already voted, delete old data. + foreach my $plyr (keys %LYNCH) { + if (exists $LYNCH{$plyr}{lc $src->{nick}}) { delete $LYNCH{$plyr}{lc $src->{nick}} } + } + + # All good, proceed. + $LYNCH{lc $argv[1]}{lc $src->{nick}} = 1; + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 votes for \2$argv[1]\2."); + + # If the accused has enough votes for lynching, call judgment. + if (keys %{$LYNCH{lc $argv[1]}} >= $LVOTEN) { _judgment(lc $argv[1]) } + } + when (/^(RETRACT|R)$/) { + # WOLF RETRACT + + # Check if a game is running. + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Only allow lynching during the day. + if ($PHASE ne 'd') { + notice($src->{svr}, $src->{nick}, 'Lynching is only allowed during the day. Please wait patiently for morning.'); + return; + } + + # Check if they've been shot today. + if ($SHOT eq lc $src->{nick}) { + notice($src->{svr}, $src->{nick}, 'You\'re wounded and resting, thus you are unable to vote for the day.'); + return; + } + + # Delete their vote. + my $hvi; + foreach my $plyr (keys %LYNCH) { + if (exists $LYNCH{$plyr}{lc $src->{nick}}) { $hvi = 1; delete $LYNCH{$plyr}{lc $src->{nick}} } + } + + # Done. + if ($hvi) { + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 retracted his/her vote."); + } + else { + notice($src->{svr}, $src->{nick}, 'You haven\'t voted yet.'); + } + } + when (/^(VOTES|V)$/) { + # WOLF VOTES + + # Check if a game is running. + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # No voting at night. + if ($PHASE ne 'd') { + notice($src->{svr}, $src->{nick}, 'Voting is only during the day.'); + return; + } + + # Return data. + if (keys %LYNCH) { + my $str; + foreach my $key (sort {keys %{$LYNCH{$b}} <=> keys %{$LYNCH{$a}}} keys %LYNCH) { + $str .= ", $NICKS{$key}: ".keys(%{$LYNCH{$key}}).' ('.join(' ', keys %{$LYNCH{$key}}).')'; + } + $str = substr $str, 2; + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: $str"); + } + else { + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: No votes yet."); + } + my $plyru = keys %PLAYERS; + if ($SHOT) { $plyru-- } + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: \2".keys(%PLAYERS)."\2 players, \2$LVOTEN\2 votes required to lynch, \2$plyru\2 players available to vote."); + } + when ('SHOOT') { + # WOLF SHOOT + + # Check if a game is running. + if (!$GAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Only allow shooting during the day. + if ($PHASE ne 'd') { + notice($src->{svr}, $src->{nick}, 'Shooting is only allowed during the day. Please wait patiently for morning.'); + return; + } + + # They must have the gun to do this. + if ($PLAYERS{lc $src->{nick}} !~ m/b/xsm) { + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: You don't have the gun."); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the target is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + notice($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # All good, lets go! + my $real = $NICKS{lc $argv[1]}; + + # Massive randomizing here. 5 = 3-kill, 1-miss, 1-suicide + my $myr = int rand 7; + given ($myr) { + when (/^[1-3]$/) { # It's a hit. + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 shot \2$real\2 with a silver bullet!"); + + # Check if the target is a wolf or a villager. + if ($PLAYERS{lc $argv[1]} =~ m/w/xsm) { # Wolf. + privmsg($src->{svr}, $src->{chan}, "\2$real\2 is a wolf, and is dying from the silver bullet."); + _player_del(lc $argv[1]); + $LVOTEN--; + } + else { # Villager. + # So, there's a 1/5 chance of a villager dying. + my $rint = int rand 6; + given ($rint) { + when (/^[1-4]$/) { # Only hurt. + privmsg($src->{svr}, $src->{chan}, "\2$real\2 is a villager, and is hurt but will have a full recovery."); + $SHOT = lc $real; + # Delete any votes they might've made today. + foreach my $plyr (keys %LYNCH) { + if (exists $LYNCH{$plyr}{lc $real}) { delete $LYNCH{$plyr}{lc $real} } + } + # Decrement LVOTEN by 1. + $LVOTEN--; + } + when (5) { # Killed. + privmsg($src->{svr}, $src->{chan}, "\2$real\2 is a villager, but \2$src->{nick}\2 accidentally shot them in the head and they are now dying."); + if (_getrole(lc $real, 2) ne 'villager') { privmsg($src->{svr}, $src->{chan}, "Appears s(he) was a \2"._getrole(lc $real, 2)."\2."); } + # Kill them. + _player_del(lc $real); + # Decrement LVOTEN by 1. + $LVOTEN--; + } + } + } + } + when (4) { # It's a miss. + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 is a lousy shooter. He/She missed!"); + } + when (5) { # Gun explodes = suicide. + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 should clean his/her weapons more often. The gun exploded and killed him/her!"); + if (_getrole(lc $src->{nick}, 2) ne 'villager') { privmsg($src->{svr}, $src->{chan}, "Appears s(he) was a \2"._getrole(lc $src->{nick}, 2)."\2."); } + _player_del(lc $src->{nick}); + $LVOTEN--; + return 1; + } + } + # Remove the gun from them. + $PLAYERS{lc $src->{nick}} =~ s/b//gxsm; + } + when (/^(STATS|S)$/) { + # WOLF STATS + + # Check if a game is running. + if (!$GAME and !$PGAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Return data. + my $str; + foreach (keys %PLAYERS) { $str .= ", $NICKS{$_}" } + $str = substr $str, 2; + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: \2".keys(%PLAYERS)."\2 players: $str"); + # If all roles have been assigned, return the count of each. + if ($GAME) { + my $cwolves = my $cseers = my $charlots = my $cangels = my $ctraitors = my $cdetectives = 0; + while ((undef, my $flags) = each %PLAYERS) { + if ($flags =~ m/w/xsm) { $cwolves++ } + if ($flags =~ m/s/xsm) { $cseers++ } + if ($flags =~ m/h/xsm) { $charlots++ } + if ($flags =~ m/g/xsm) { $cangels++ } + if ($flags =~ m/t/xsm) { $ctraitors++ } + if ($flags =~ m/d/xsm) { $cdetectives++ } + } + privmsg($src->{svr}, $src->{chan}, "$src->{nick}: There are \2$cwolves\2 wolves, \2$cseers\2 seers, ". + "\2$charlots\2 ".((conf_get('werewolf:rated-g')) ? '' : 'harlots').", \2$ctraitors\2 traitors, \2$cdetectives\2 detectives, ". + "and \2$cangels\2 guardian angels."); + } + } + when (/^(KICK|K)$/) { + # WOLF KICK + + # Check if a game is running. + if (!$GAME and !$PGAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Check for the werewolf.admin privilege. + if (!has_priv(match_user(%{$src}), 'werewolf.admin')) { + notice($src->{svr}, $src->{nick}, trans('Permission denied').q{.}); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + notice($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Kill the target. + privmsg($src->{svr}, $src->{chan}, "\2$argv[1]\2 died of an unknown disease. He/She was a \2"._getrole(lc $argv[1], 2)."\2."); + _player_del(lc $argv[1]); + } + when (/^(QUIT|Q)$/) { + # WOLF QUIT + + # Check if a game is running. + if (!$GAME and !$PGAME) { + notice($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Check if this is the game channel. + if ($src->{svr}.'/'.$src->{chan} ne $GAMECHAN) { + notice($src->{svr}, $src->{nick}, "Werewolf is currently running in \2$GAMECHAN\2."); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Kill the target. + privmsg($src->{svr}, $src->{chan}, "\2$src->{nick}\2 died of an unknown disease. He/She was a \2"._getrole(lc $src->{nick}, 2)."\2."); + _player_del(lc $src->{nick}); + } + default { notice($src->{svr}, $src->{nick}, trans('Unknown action', $_).q{.}); } + } + } + else { + given (uc $argv[0]) { + # Iterate the parameter. + when ('SEE') { + # WOLF SEE + + # Check if a game is running. + if (!$GAME) { + privmsg($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if they are the seer. + if ($PLAYERS{lc $src->{nick}} !~ m/s/xsm) { + privmsg($src->{svr}, $src->{nick}, 'Only the seer may use this command.'); + return; + } + + # Check if it's nighttime. + if ($PHASE ne 'n') { + privmsg($src->{svr}, $src->{nick}, 'You may only have visions at night.'); + return; + } + + # Check if they've already had a vision. + if ($SEEN) { + privmsg($src->{svr}, $src->{nick}, 'You may only have one vision per round.'); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + privmsg($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the requested nick is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + privmsg($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # All good, proceed. + privmsg($src->{svr}, $src->{nick}, "You have a vision, in this vision, you see that \2$argv[1]\2 is a... \2"._getrole(lc $argv[1], 1)."\2!"); + $SEEN = 1; + # Run nighttime force end check. + _chknight(); + } + when ('ID') { + # WOLF ID + + # Check if a game is running. + if (!$GAME) { + privmsg($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if they are the detective. + if ($PLAYERS{lc $src->{nick}} !~ m/d/xsm) { + privmsg($src->{svr}, $src->{nick}, 'Only the detective may use this command.'); + return; + } + + # Check if it's daytime. + if ($PHASE ne 'd') { + privmsg($src->{svr}, $src->{nick}, 'You may only investigate people during the day.'); + return; + } + + # Check if they've already investigated someone. + if ($DETECTED) { + privmsg($src->{svr}, $src->{nick}, 'You may only investigate one person per round.'); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + privmsg($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the requested nick is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + privmsg($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # All good, proceed. + privmsg($src->{svr}, $src->{nick}, "The results of your investigation have returned. \2$argv[1]\2 is a... \2"._getrole(lc $argv[1], 2)."\2!"); + $DETECTED = 1; + + # Now, take the 2/5 chance of revealing their identity to the wolves. + my $rint = int rand 6; + if ($rint =~ m/^(1|5)$/xsm) { + # Ouch. Gotta rat 'em out. + while (my ($ruser, $rflags) = each %PLAYERS) { + if ($rflags =~ m/w/xsm or $rflags =~ m/t/xsm) { + privmsg($src->{svr}, $NICKS{$ruser}, "\2$src->{nick}\2 accidentally drops a paper. The paper reveals that (s)he is the detective!"); + } + } + } + } + when ('GUARD') { + # WOLF GUARD + + # Check if a game is running. + if (!$GAME) { + privmsg($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if they are the guardian angel. + if ($PLAYERS{lc $src->{nick}} !~ m/g/xsm) { + privmsg($src->{svr}, $src->{nick}, 'Only the guardian angel may use this command.'); + return; + } + + # Check if it's nighttime. + if ($PHASE ne 'n') { + privmsg($src->{svr}, $src->{nick}, 'You may only protect people at night.'); + return; + } + + # They can only protect one person per round. + if ($GUARD) { + privmsg($src->{svr}, $src->{nick}, "You are already protecting \2$NICKS{$VISIT}\2."); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + privmsg($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the requested nick is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + privmsg($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # They cannot guard themselves. + if (lc $argv[1] eq lc $src->{nick}) { + privmsg($src->{svr}, $src->{nick}, 'You may not guard yourself!'); + return; + } + + # All good, proceed. + $GUARD = lc $argv[1]; + privmsg($src->{svr}, $src->{nick}, "You are protecting \2$argv[1]\2 for the night. Farewell!"); + privmsg($src->{svr}, $NICKS{lc $argv[1]}, 'You can sleep well tonight, for the guardian angel is protecting you.'); + # Run nighttime force end check. + _chknight(); + } + when ('VISIT') { + # WOLF VISIT + + # Check if a game is running. + if (!$GAME) { + privmsg($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if they are the harlot. + if ($PLAYERS{lc $src->{nick}} !~ m/h/xsm) { + privmsg($src->{svr}, $src->{nick}, 'Only the harlot may use this command.'); + return; + } + + # Check if it's nighttime. + if ($PHASE ne 'n') { + privmsg($src->{svr}, $src->{nick}, 'You may only visit people at night.'); + return; + } + + # They can only visit one person per round. + if ($VISIT) { + privmsg($src->{svr}, $src->{nick}, "You are already spending the night with \2$NICKS{$VISIT}\2!"); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + privmsg($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the requested nick is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + privmsg($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # They can't sleep with themselves. + if (lc $argv[1] eq lc $src->{nick}) { + privmsg($src->{svr}, $src->{nick}, 'You cannot sleep with yourself!'); + return; + } + + # All good, set variables and stuff. + $VISIT = lc $argv[1]; + privmsg($src->{svr}, $src->{nick}, "You are spending the night with \2$argv[1]\2. Have a good time!"); + privmsg($src->{svr}, $NICKS{lc $argv[1]}, "You are spending the night with \2$src->{nick}\2, the village harlot. Have a good time!"); + # Run nighttime force end check. + _chknight(); + } + when ('KILL') { + # WOLF KILL + + # Check if a game is running. + if (!$GAME) { + privmsg($src->{svr}, $src->{nick}, 'No game is currently running.'); + return; + } + + # Make sure they're playing. + if (!defined $PLAYERS{lc $src->{nick}}) { + notice($src->{svr}, $src->{nick}, 'You\'re not currently playing.'); + return; + } + + # Check if they are a wolf. + if ($PLAYERS{lc $src->{nick}} !~ m/w/xsm) { + privmsg($src->{svr}, $src->{nick}, 'Only a wolf may use this command.'); + return; + } + + # Check if it's nighttime. + if ($PHASE ne 'n') { + privmsg($src->{svr}, $src->{nick}, 'You may only kill people at night.'); + return; + } + + # Requires an extra parameter. + if (!defined $argv[1]) { + privmsg($src->{svr}, $src->{nick}, trans('Not enough parameters').q{.}); + return; + } + + # Check if the requested nick is playing. + if (!exists $PLAYERS{lc $argv[1]}) { + privmsg($src->{svr}, $src->{nick}, "\2$argv[1]\2 is not currently playing."); + return; + } + + # Check if they're trying to kill themselves. + if (lc $argv[1] eq lc $src->{nick}) { + privmsg($src->{svr}, $src->{nick}, 'Suicide is bad, don\'t do it.'); + return; + } + + # Don't let them kill other wolves. + if ($PLAYERS{lc $argv[1]} =~ m/w/xsm) { + privmsg($src->{svr}, $src->{nick}, 'You may only kill villagers, not other wolves.'); + return; + } + + # If they're changing their selection, reverse the old victim's data. + if ($WKILL{lc $src->{nick}}) { $KILL{lc $WKILL{lc $src->{nick}}}-- } + + # All good, proceed. + $KILL{lc $argv[1]}++; + $WKILL{lc $src->{nick}} = lc $argv[1]; + privmsg($src->{svr}, $src->{nick}, "You have selected \2$argv[1]\2 to be killed."); + # Run nighttime force end check. + _chknight(); + } + default { notice($src->{svr}, $src->{nick}, trans('Unknown action', $_).q{.}); } + } + } + + return 1; +} + +# Change time to night. +sub _init_night { + my ($gsvr, $gchan) = split '/', $GAMECHAN; + + # Iterate through all players. + foreach my $plyr (keys %PLAYERS) { + my $role = $PLAYERS{$plyr}; + # Check if they have a special role. + if ($role =~ m/(w|h|s|g|d|t)/xsm) { + my ($erole, $msg); + # For wolves. + if ($role =~ m/w/xsm) { + $erole = 'wolf'; + $msg = "It is your job to kill all the villagers. Use \"$COMMANDS{kill}\" to kill a villager. Also, if you send a PM to me, it will be relayed to all other wolves.\n&pi&"; + $msg .= "\nAlso, if there are other wolves, please consider using PM's or a channel to speak with them rather than my relay, to prevent from me lagging."; + } + # For harlots. + elsif ($role =~ m/h/xsm) { + $erole = 'harlot'; + $msg = "You may spend the night with one person per round. If you visit a victim of a wolf, or visit a wolf, you will die. Use \"$COMMANDS{visit}\" to visit a player.\n&ph&"; + } + # For seers. + elsif ($role =~ m/s/xsm) { + $erole = 'seer'; + $msg = "It is your job to detect the wolves, you may have a vision once per night. Use \"$COMMANDS{see}\" to see the role of a player.\n&ph&"; + } + # For guardian angels. + elsif ($role =~ m/g/xsm) { + $erole = 'guardian angel'; + $msg = "It is your job to protect the villagers. If you guard a wolf, there is a 50/50 chance of you dying, if you guard a victim, they will live. Use \"$COMMANDS{guard}\" to guard a player.\n&ph&"; + } + # For detectives. + elsif ($role =~ m/d/xsm) { + $erole = 'detective'; + $msg = "It is your job to determine all the wolves and traitors. Your job is during the day, and you can see the true identity of all users, even traitors."; + $msg .= "\nBut, each time you use your ability, you risk a 2/5 chance of having your identity revealed to the wolves. So be careful. Use \"$COMMANDS{id}\" to identify any player during the day."; + } + # For traitors. + elsif ($role =~ m/t/xsm) { + $erole = 'traitor'; + $msg = "You are a villager, but are on the side of the wolves. You're exactly like a villager and not even a seer can see your true identity. Only detectives can.\n&pi&"; + $msg .= "\nAll your messages are relayed to the wolves and vice versa, but, to contact the wolves, consider using PM's or a channel to speak with them rather than my relay, to prevent from me lagging."; + } + + # Split message at \n. + my @data = split "\n", $msg; + + # Set &pi& and &ph&. + my $pi; + while (my ($pnick, $prole) = each %PLAYERS) { + if ($pnick eq $plyr) { next } + $pi .= ", $NICKS{$pnick}"; + if ($prole =~ m/w/xsm) { $pi .= ' (wolf)' } + if ($prole =~ m/t/xsm) { $pi .= ' (traitor)' } + } + $pi =~ s/^,\s//xsm; + $data[1] =~ s/&pi&/Players: $pi/gxsm; + my $ph; + while ((my $pnick, undef) = each %PLAYERS) { if ($pnick eq $plyr) { next }; $ph .= ", $NICKS{$pnick}" } + $ph =~ s/^,\s//xsm; + $data[1] =~ s/&ph&/Players: $ph/gxsm; + + # Give them their information. + privmsg($gsvr, $NICKS{$plyr}, "You are a \2$erole\2. $data[0]"); + privmsg($gsvr, $NICKS{$plyr}, $data[1]); + if ($data[2]) { privmsg($gsvr, $NICKS{$plyr}, $data[2]) } + } + # Check if they have the gun. + if ($role =~ m/b/xsm) { + privmsg($gsvr, $NICKS{$plyr}, 'You hold a special gun. You may only use it during the day. If you shoot a wolf, (s)he will die instantly, if you shoot a villager, they will likely live. You only get one shot, so use it wisely.'); + privmsg($gsvr, $NICKS{$plyr}, 'To shoot someone, type "'.$FCHAR.$COMMANDS{shoot}.'" in the channel during the day.'); + } + } + + # It is now nighttime. + $PHASE = 'n'; + %LYNCH = (); + $DETECTED = 0; + privmsg($gsvr, $gchan, 'It is now nighttime. All players check for PM\'s from me for instructions. If you did not receive one, simply sit back, relax, and wait patiently for morning.'); + timer_add('werewolf.goto_daytime', 1, 90, sub { M::Werewolf::_init_day() }); + + return 1; +} + +# Change time to day. +sub _init_day { + my ($gsvr, $gchan) = split '/', $GAMECHAN; + + # We need wolf count. + my $wolves = 0; + while ((undef, $_) = each %PLAYERS) { if ($_ =~ m/w/xsm) { $wolves++ } } + # For starters, lets determine if there's a victim to kill. + my $victim; + if (keys %KILL) { + # First lets figure out if we have multiple wolves. + if ($wolves == 1) { + ($victim) = keys %KILL; + } + else { + my $victv = 0; + # Check which player had the most votes. If all wolves gave different votes, choose a random one. + while (my ($plyr, $votec) = each %KILL) { + if ($votec > $victv) { $victv = $votec; $victim = $plyr } + } + } + } + + # Now check if the guardian angel protected the victim last night. + if ($victim and $GUARD) { + # If the victim was protected, set $victim to 1. + if ($GUARD eq $victim) { $victim = 1 } + } + + # Now check if the harlot spent the night with the victim last night. + my $harlotd = 0; + if (defined $victim and $VISIT) { + if ($victim !~ m/^(0|1)$/xsm) { + if ($VISIT eq $victim) { + # Ouch! He/She did, gotta kill them. :( + $harlotd = 1; + } + } + } + # Now check if the harlot spent the night with a wolf last night. + if (!$harlotd and $VISIT) { + if ($PLAYERS{$VISIT} =~ m/w/xsm) { + # Ouch! He/She did, gotta kill them. :( + $harlotd = 2; + } + } + + # Last check; lets find out if the guardian angel protected a wolf last night. + my $angeld; + if ($GUARD) { + if ($PLAYERS{$GUARD} =~ m/w/xsm) { + # Ouch. He/She did, so determine if they die or not. (50/50 chance) + my $rint = int rand 11; + given ($rint) { + when (/^(1|3|5|7|9)$/) { $angeld = 0 } + when (/^(2|4|6|8|10)$/) { $angeld = 1 } + default { $angeld = 0 } + } + } + } + + # Finally, check if the harlot was targeted and wasn't home. + if (_getrole($victim, 2) eq 'harlot' and $VISIT) { + privmsg($gsvr, $gchan, 'The wolves attempted to attack the harlot last night, but (s)he wasn\'t home...'); + $victim = 0; + } + + # Cool, all data should be ready for shipment. Lets go! + my $msg = 'It is now daytime. The villagers awake, thankful for surviving the night, and search the village...'; + if (!$victim) { privmsg($gsvr, $gchan, "$msg The body of a young penguin pet is found. All villagers however, have survived.") } + elsif ($victim eq 1) { + privmsg($gsvr, $gchan, "$msg \2$NICKS{$GUARD}\2 was attacked by the wolves last night, but luckily, the guardian angel protected them."); + } + else { + privmsg($gsvr, $gchan, "$msg The dead body of \2$NICKS{$victim}\2, a \2"._getrole($victim, 2)."\2, is found. Those remaining mourn his/her death."); + _player_del($victim); + if ($harlotd == 1) { + my $harlot; + while (my ($cuser, $crole) = each %PLAYERS) { if ($crole =~ m/h/xsm) { $harlot = $cuser } } + if ($harlot) { # Unusual cases call for this. + privmsg($gsvr, $gchan, "\2$NICKS{$harlot}\2, the village harlot, made the unfortunate mistake of visiting the victim last night, and is now dead."); + _player_del($harlot); + } + } + } + # Check if the harlot visited a wolf. + if ($harlotd == 2) { + my $harlot; + while (my ($cuser, $crole) = each %PLAYERS) { if ($crole =~ m/h/xsm) { $harlot = $cuser } } + if ($harlot) { # Unusual cases call for this. + privmsg($gsvr, $gchan, "\2$NICKS{$harlot}\2, the village harlot, made the unfortunate mistake of visiting a wolf last night, and is now dead."); + _player_del($harlot); + } + } + # Check if the angel guarded a wolf. + if ($angeld) { + my $angel; + while (my ($cuser, $crole) = each %PLAYERS) { if ($crole =~ m/g/xsm) { $angel = $cuser } } + if ($angel) { # Unusual cases call for this. + privmsg($gsvr, $gchan, "\2$NICKS{$angel}\2, the guardian angel, made the unfortunate mistake of guarding a wolf last night, attempted to escape, but failed and is now dead."); + _player_del($angel); + } + } + + # Set number of votes required to lynch. + $LVOTEN = int(scalar(keys(%PLAYERS)) / 2); + $LVOTEN++; + privmsg($gsvr, $gchan, 'The villagers must now vote for who to lynch. Use "'.$FCHAR.$COMMANDS{lynch}.'" to cast your vote. '.$LVOTEN.' votes are required to lynch.'); + + # Set phase to day and clear variables. + $PHASE = 'd'; + $SEEN = $GUARD = $VISIT = $SHOT = 0; + %KILL = (); + %WKILL = (); + + return 1; +} + +# Subroutine for checking if we should force the night to end. +sub _chknight { + # Get a current count of each role. + my $cwolves = my $cseers = my $charlots = my $cangels = 0; + while ((undef, my $flags) = each %PLAYERS) { + if ($flags =~ m/w/xsm) { $cwolves++ } + if ($flags =~ m/s/xsm) { $cseers++ } + if ($flags =~ m/h/xsm) { $charlots++ } + if ($flags =~ m/g/xsm) { $cangels++ } + } + + # Lets start with checking if all wolves have voted. + if (keys %WKILL != $cwolves) { + # They haven't, return. + return; + } + # Next check if the seer has had their vision. + if ($cseers && !$SEEN) { + # They haven't, return. + return; + } + # Now check if the harlot has chosen someone to visit. + if ($charlots && !$VISIT) { + # They haven't, return. + return; + } + # Finally check if the angel has chosen someone to guard. + if ($cangels && !$GUARD) { + # They haven't, return. + return; + } + + # All players have fulfilled their respective roles. Thus, it is + # pointless to wait for morning. Delete night timeout timer and force + # daytime initialization. + timer_del('werewolf.goto_daytime'); + _init_day(); + + return 1; +} + +# For performing lynches. +sub _judgment { + my ($nick) = @_; + + # Kill him/her! + my ($gsvr, $gchan) = split '/', $GAMECHAN; + privmsg($gsvr, $gchan, "The villagers, after much debate, finally decide on lynching \2$NICKS{$nick}\2, who turned out to be... a \2"._getrole($nick, 2)."\2."); + my $ri = _player_del($nick); + + if ($ri) { + my @warn; + # Check if any players failed to vote. + foreach my $plyr (keys %PLAYERS) { + my $bi; + foreach my $acpl (keys %LYNCH) { + if (exists $LYNCH{$acpl}{$plyr}) { $bi = 1 } + } + if (!$bi) { + # This player in particular didn't vote today, increase their bed variable. + # Also check if they should be kicked from the game (2 days without voting). + if (++$BED{$plyr} >= 2) { + privmsg($gsvr, $gchan, "\2$NICKS{$plyr}\2 didn't get out of bed for two days. He/She is declared dead. Appears (s)he was a \2"._getrole($plyr, 2)."\2."); + $ri = _player_del($plyr); + last unless $ri; + } + else { # They didn't vote for today only. Just give them a friendly warning. + push @warn, $NICKS{$plyr}; + } + } + } + if ($ri and scalar @warn) { + privmsg($gsvr, , join(' ', @warn).': You didn\'t vote today. Please remember to say something in the channel soon or you might be declared dead.') + } + } + + # Initialize nighttime. + if ($ri) { _init_night() } + + return 1; +} + +# Return the role of a player. +sub _getrole { + my ($plyr, $lev) = @_; + + my $role; + if ($lev == 1) { + if ($PLAYERS{$plyr} =~ m/w/xsm) { $role = 'wolf' } + elsif ($PLAYERS{$plyr} =~ m/s/xsm) { $role = 'seer' } + elsif ($PLAYERS{$plyr} =~ m/h/xsm) { $role = 'harlot' } + elsif ($PLAYERS{$plyr} =~ m/g/xsm) { $role = 'guardian angel' } + elsif ($PLAYERS{$plyr} =~ m/d/xsm) { $role = 'detective' } + else { $role = 'villager' } + } + elsif ($lev == 2) { + if ($PLAYERS{$plyr} =~ m/w/xsm) { $role = 'wolf' } + elsif ($PLAYERS{$plyr} =~ m/s/xsm) { $role = 'seer' } + elsif ($PLAYERS{$plyr} =~ m/h/xsm) { $role = 'harlot' } + elsif ($PLAYERS{$plyr} =~ m/g/xsm) { $role = 'guardian angel' } + elsif ($PLAYERS{$plyr} =~ m/d/xsm) { $role = 'detective' } + elsif ($PLAYERS{$plyr} =~ m/t/xsm) { $role = 'traitor' } + else { $role = 'villager' } + } + if (!$role) { $role = 'person' } + + return $role; +} + +# Delete a player. +sub _player_del { + my ($player) = @_; + my ($gsvr, $gchan) = split '/', $GAMECHAN; + + # Devoice them. + cmode($gsvr, $gchan, "-v $NICKS{$player}"); + + # Delete variables. + delete $PLAYERS{$player}; + delete $NICKS{$player}; + delete $BED{$player}; + if (exists $KILL{$player}) { delete $KILL{$player} } + foreach my $acpl (keys %LYNCH) { + if (exists $LYNCH{$acpl}{$player}) { delete $LYNCH{$acpl}{$player} } + } + foreach my $acpl (keys %LYNCH) { + if ($acpl eq $player) { delete $LYNCH{$acpl} } + } + if ($VISIT) { if ($VISIT eq $player) { $VISIT = 0 } } + if ($GUARD) { if ($GUARD eq $player) { $GUARD = 0 } } + if ($SHOT) { if ($SHOT eq $player) { $SHOT = 0 } } + if ($SEEN) { if ($SEEN eq $player) { $SEEN = 0 } } + + # Check for winning conditions. + my $wolves; + while ((undef, $_) = each %PLAYERS) { if ($_ =~ m/(w|t)/xsm) { $wolves++ } } + if (!$wolves) { _gameover('v'); return } + my $villagers; + while ((undef, $_) = each %PLAYERS) { if ($_ =~ m/(v|s|g|h|d)/xsm) { $villagers++ } } + if ($villagers <= $wolves) { _gameover('w'); return } + + return 1; +} + +# Handling the end of the game. +sub _gameover { + my ($winner) = @_; + my ($gsvr, $gchan) = split '/', $GAMECHAN; + + if ($winner eq 'v') { # The villagers won! + privmsg($gsvr, $gchan, 'Game over! All the wolves are dead! The villagers chop them up, BBQ them, and have a hearty meal.'); + } + elsif ($winner eq 'w') { # The wolves won! + privmsg($gsvr, $gchan, 'Game over! There is the same amount of wolves as villagers. The wolves eat everyone, and win.'); + } + my $smsg = "The wolves were $STATIC[0]. The seer was $STATIC[1]."; + if ($STATIC[2]) { $smsg .= " The harlot was $STATIC[2]." } + if ($STATIC[3]) { $smsg .= " The guardian angel was $STATIC[3]." } + if ($STATIC[4]) { $smsg .= " The traitor was $STATIC[4]." } + if ($STATIC[5]) { $smsg .= " The detective was $STATIC[5]." } + privmsg($gsvr, $gchan, $smsg); + + # Set -m, unless explicitly told not to in config. + if (!conf_get('werewolf:always-m')) { cmode($gsvr, $gchan, '-m') } + + # Devoice all users. + my ($gc, $gn, @gu); + $gc = $gn = 0; + while ((undef, my $nick) = each %NICKS) { + $gu[$gc] .= " $nick"; + $gn++; + if ($gn > 3) { $gn = 0; $gc++ } + } + foreach (@gu) { cmode($gsvr, $gchan, "-vvvv$_") } + + # Clear all variables. + if ($PHASE eq 'n') { timer_del('werewolf.goto_daytime') } + $GAME = $PGAME = $GAMECHAN = $GAMETIME = $WAIT = $PHASE = $SEEN = $VISIT = $GUARD = $LVOTEN = $SHOT = $DETECTED = 0; + %PLAYERS = (); + %NICKS = (); + %KILL = (); + %WKILL = (); + %LYNCH = (); + %BED = (); + @STATIC = (); + + return 1; +} + +# Handle channel messages. +sub on_cprivmsg { + my ($src, $chan, undef) = @_; + + # First, check if a game is running. + if ($GAME) { + # Check if this is the game channel. + if (lc $src->{svr}.'/'.$chan eq lc $GAMECHAN) { + # Check if this person is playing. + if (exists $PLAYERS{lc $src->{nick}}) { + # Reset their bed variable to 0. + $BED{lc $src->{nick}} = 0; + } + } + } + + return 1; +} + +# Handle private messages. +sub on_uprivmsg { + my ($src, @msg) = @_; + + # Check if a game is running. + if ($GAME) { + # Ensure no-wolfrelay is not set in config. + if (!conf_get('werewolf:no-wolfrelay')) { + # Check if this person is playing. + if (exists $PLAYERS{lc $src->{nick}}) { + # Check if they're a wolf/traitor. + if ($PLAYERS{lc $src->{nick}} =~ m/w/xsm or $PLAYERS{lc $src->{nick}} =~ m/t/xsm) { + # Get wolf count. + my $cwolves = 0; + while ((undef, my $flags) = each %PLAYERS) { if ($flags =~ m/w/xsm) { $cwolves++ } } + # If there's more than one wolf, relay this message to the others. + if ($cwolves > 1) { + while ((my $plyr, my $flags) = each %PLAYERS) { + if ($plyr ne lc $src->{nick} and $flags =~ m/w/xsm or $flags =~ m/t/xsm) { + privmsg($src->{svr}, $NICKS{$plyr}, "\2$src->{nick}\2 says: ".join(q{ }, @msg)) + } + } + } + } + } + } + } + + return 1; +} + +# Handle nick changes. +sub on_nick { + my ($src, $newnick) = @_; + my $new = lc $newnick; + + # Check if a game is running. + if ($GAME or $PGAME) { + # Check if they're playing. + if (exists $PLAYERS{lc $src->{nick}}) { + # Time to update their data. + # Hopefully this will work right, but if it doesn't, someone please make a bug report. + $PLAYERS{$new} = $PLAYERS{lc $src->{nick}}; + $NICKS{$new} = $newnick; + $BED{$new} = $BED{lc $src->{nick}}; + delete $PLAYERS{lc $src->{nick}}; + delete $NICKS{lc $src->{nick}}; + delete $BED{lc $src->{nick}}; + if ($SHOT) { if ($SHOT eq lc $src->{nick}) { $SHOT = $new } } + if ($GUARD) { if ($GUARD eq lc $src->{nick}) { $GUARD = $new } } + if ($VISIT) { if ($VISIT eq lc $src->{nick}) { $VISIT = $new } } + if (exists $KILL{lc $src->{nick}}) { + $KILL{$new} = $KILL{lc $src->{nick}}; + delete $KILL{lc $src->{nick}}; + } + if (exists $WKILL{lc $src->{nick}}) { + $WKILL{$new} = $WKILL{lc $src->{nick}}; + delete $WKILL{lc $src->{nick}}; + } + if (exists $LYNCH{lc $src->{nick}}) { + $LYNCH{$new} = $LYNCH{lc $src->{nick}}; + delete $LYNCH{lc $src->{nick}}; + } + while (my ($accu, $ser) = each %LYNCH) { + if ($ser eq lc $src->{nick}) { + $LYNCH{$accu}{$ser} = 1; + delete $LYNCH{$accu}{lc $src->{nick}}; + } + } + # That should be everything. + } + } + + return 1; +} + +# Handle channel parts. +sub on_part { + my ($src, $chan, undef) = @_; + + # Check if a game is running. + if ($GAME or $PGAME) { + # Check if this is the game channel. + if (lc $src->{svr}.'/'.$chan eq lc $GAMECHAN) { + # If they're playing, they're not anymore. + if (exists $PLAYERS{lc $src->{nick}}) { + my ($gsvr, $gchan) = split '/', $GAMECHAN; + privmsg($gsvr, $gchan, "\2$src->{nick}\2 died due to eating poisonous berries. Appears (s)he was a \2"._getrole(lc $src->{nick}, 2)."\2."); + _player_del(lc $src->{nick}); + } + } + } + + return 1; +} + +# Handle channel kicks. +sub on_kick { + my ($src, $chan, $user, undef) = @_; + + # Check if a game is running. + if ($GAME or $PGAME) { + # Check if this is the game channel. + if (lc $src->{svr}.'/'.$chan eq lc $GAMECHAN) { + # If they're playing, they're not anymore. + if (exists $PLAYERS{lc $user}) { + my ($gsvr, $gchan) = split '/', $GAMECHAN; + privmsg($gsvr, $gchan, "\2$user\2 died due to falling off a cliff. Appears (s)he was a \2"._getrole(lc $user, 2)."\2."); + _player_del(lc $user); + } + } + } + + return 1; +} + +# Handle network disconnects. +sub on_quit { + my ($src, $msg) = @_; + + # Check if a game is running. + if ($GAME or $PGAME) { + # If they're playing, continue. + if (exists $PLAYERS{lc $src->{nick}}) { + # First make sure it's not chghost-quit. + if (conf_get('werewolf:chghost-quit')) { + if ($msg eq (conf_get('werewolf:chghost-quit'))[0][0]) { return } + } + + # Kill them. + my ($gsvr, $gchan) = split '/', $GAMECHAN; + privmsg($gsvr, $gchan, "\2$src->{nick}\2 died due to a fatal attack by wild animals. Appears (s)he was a \2"._getrole(lc $src->{nick}, 2)."\2."); + _player_del(lc $src->{nick}); + } + } + + return 1; +} + +# Handle bot rehashes. +sub on_rehash { + # Reload fantasy prefix into memory. + $FCHAR = (conf_get('fantasy_pf'))[0][0]; + + return 1; +} + +# Start initialization. +API::Std::mod_init('Werewolf', 'Xelhua', '1.00', '3.0.0a10'); +# build: perl=5.010000 + +__END__ + +=head1 NAME + +Werewolf - IRC version of the Werewolf detective/social party game + +=head1 VERSION + + 1.00 + +=head1 SYNOPSIS + + !wolf start + * blue gives voice to starcoder + starcoder has started a game of Werewolf. Type "!wolf join" to join. Type "!wolf begin" to begin the game. + !wolf j + * blue gives voice to starcoder-- + starcoder-- joined the game. + !wolf j + * blue gives voice to starcoder--- + starcoder--- joined the game. + !wolf j + * blue gives voice to starcoder_ + starcoder_ joined the game. + !wolf b + starcoder_: Please wait at least 48 more seconds. + !wolf b + Game is now starting. + * blue sets mode +m #bot + It is now nighttime. All players check for PM's from me for instructions. If you did not receive one, simply sit back, relax, and wait patiently for morning. + +=head1 DESCRIPTION + +Well, this module is a rather advanced version of the popular Werewolf (also +known as Mafia) detective/social party game, redesigned and optimizied for +Internet Relay Chat. + +It, obviously, is a complete module for Auto, and has absolutely no extra +dependencies. + +It includes a whopping eight player roles, four of which are optional. See the +ROLES section for more information on them. + +If you want the best out of Werewolf, please be sure to read over CONFIGURATION. + +It is intended to be highly advanced, consistent, clean, well-documented, +minimal, and of course, customizable. + +Emphasis on advanced. Kudos to those of you non-Xelhua developers who can +figure it out, even with the large amounts of comments. :) + +While you might find this documentation to be comical, know that by all means, +we are serious when we say: RTFM (Read The Fine Manual), before asking for help. + +Oh, and one more thing, for the sake of examples, we use ! as the prefix for +the commands. However, this might differ depending on your fantasy_pf config +option. Also, naturally, commands might differ if you're one of the cool kids, +and use aliases. + +=head1 COMMANDS + +Here is a list of channel commands: + + WOLF START - Start a game. + WOLF JOIN|J - Join a game. + WOLF BEGIN|B - Begin the game. + WOLF LYNCH|L - Cast vote for who to lynch. + WOLF RETRACT|R - Retract lynch vote. + WOLF VOTES|V - Return current votes. + WOLF SHOOT - Shoot someone with the gun. + WOLF STATS|S - Return current player statistics. + WOLF KICK|K - Kick a player from the game. + WOLF QUIT|Q - Leave the game. + +Here is a list of private commands: + + WOLF SEE - See a player. + WOLF KILL - Vote to kill a player. + WOLF VISIT - Visit a player. + WOLF GUARD - Guard a player. + WOLF ID - Investigate a player. + +The WOLF KICK command requires the werewolf.admin privilege. + +=head1 RULES + +Since the rules for this particular version of Werewolf differ from the +original significantly. This section will go over *ALL* the game rules. + +=over + +=item Basics + +Minimum amount of players is four. Maximum amount of players is thirty. + +In all games, there will be at least one wolf, one seer, and two villagers. +Roles are again, explained more thoroughly in ROLES. + +Typing "!wolf start" in a channel starts a game, and a 60 second timer. Others +can join the game with "!wolf join". "!wolf begin" begins play. + +BEGIN will fail if the 60 second timer has not stopped and there is less than +the maximum amount of players. + +The bot moderates the channel (it voices all players). + +Now the game begins, it starts at night. + +=item Night + +Nighttime is considered by many to be the funnest part of the game, because it +is when the special people get to do their main jobs. + +During the night is when the bad guys, the werewolves, or wolves for short, +vote on what villager to kill. They can use Auto's private relay, or they can be +nice, and use PM's/channels, to prevent from lagging poor Auto. + +Also during the night, is when the seers, harlots and guardian angels take care +of their assigned duties. + +The night lasts a maximum of 90 seconds (that's a minute and a half, noob.), +unless all nightly jobs are fulfilled, in which case, Auto will cut down the +boredom, and forcibly initialize daytime. + +=item Day + +Day can also be interesting, because this is when the villagers are awake and +are deciding whose life is next to go. ...Erm, that sounded bad, allow me to +explain. + +In the morning, whoever the wolves voted to kill (majority vote wins), is +removed from game play and their role is announced. They're out and are thus +devoiced as dead people can't talk. (DON'T SHARE INFO AFTER DEATH, CHEATER +ZOMBIE.) + +So, out of rage, I mean, "justice", the villagers seek to avenge their fallen +mate, by lynching. Yes, lynching. No court, trial, judge, lawyer, evidence... +None of that silly stuff, just good old execution based on guesses. :D + +(If you don't know what lynching is, please see http://www.google.com) + +So, the villagers now hold their own vote on who to lynch. First person to get +(player count / 2 + 1 (bot will say the number for those of you who failed in +math.)) votes, gets killed, instantly, with their role being revealed +afterwards. + +Also, the great, Xelhua Development Group, does not consider the lynched person, +given they're a villager, PM'ing their friends to say "I TOLD YOU SO!", to be +cheating. + +Of course, lynching isn't done entirely on guessing. The seer, harlot and +detective are helpful. BUT ARE THEY WHO THEY CLAIM TO BE?! THEY COULD BE A WOLF! + +=item Winning + +There are two conditions that cause the game to end. Here they are: + +* All wolves are killed. If this happens, regardless of the way they died, the +villagers are declared the winners. (derp) + +* Equal amount of wolves as villagers. Poor villagers, wolves eat 'em all and +claim victory. + +And, if you want to be technical, there is a third cause: + +* Bot suffers a fatal exception, thus resulting in a crash and the game ending. +No winner, sorry. + +=item In Case It's Not Totally Clear + +NOTE: Some of this is borrowed from http://eblong.com/zarf/werewolf.html + +The villagers are trying to figure out who's a werewolf; the werewolves are +pretending to be villagers, and trying to throw suspicion on real villagers. + +The seer is trying to throw suspicion on any werewolves he discovers, but +without revealing himself to be the seer (because if he does, the werewolves +will almost certainly kill him that night, since he's the greatest threat to +werewolf national security.) Of course the seer can reveal himself at any time, +if he thinks it's worthwhile to tell the other players what he's learned. Also +of course, a werewolf can claim to be the seer and "reveal" anything he wants. + +The harlot and detective are the same, but have slightly different methods and +protections. See ROLES. + +Also, to be technical, since the detective can find traitors but the seer and +harlot can't, he is technically the greatest threat to werewolves. + +So... yeah... major game of trust and lying. :/ DON'T LET THE WOLVES FOOL YOU! + +=back + +=head1 ROLES + +Now, lets get to the thorough explanation of each role. + +=over + +=item Villager + +The villager is an innocent human, and does nothing but vote on who to lynch. + +=item Werewolf + +The wolves are the opposite of the villagers. They're the bad guys and their +job is to kill all the villagers, scoring them victory. + +At night, the werewolves are each given a list of players, with traitors and +fellow wolves indicated by (traitor) or (wolf). + +Just to be clear, the traitor is the traitor of the villagers, not the wolves. + +The wolves and traitors can communicate with each other simply by sending +private messages to the bot. ALL messages (even ones with passwords) are +relayed to the other wolves and traitors. So please have a dedicated instance +of Auto for this game. + +They can use the "wolf kill " command in PM to the bot. Majority wins, or +if all are equal, a random victim is selected from the votes. + +During the day, the wolves at all costs (even if it means ratting out their own) +make the villagers believe they are innocent. You need to be clever in order to +survive. + +Werewolves are assigned as follows: + + 4-8 players One werewolf. + 9-14 players Two werewolves. + 15-20 players Three werewolves. + 21-24 players Four werewolves. + 25-29 players Five werewolves. + 30 players Six werewolves. + +=item Villager (gun holder) + +If a game has 6+ players, one villager (can also be seer/harlot/etc., but not +wolves or traitors.), will be given a gun with a single special silver bullet. + +During the day, (s)he can use "!wolf shoot " to shoot someone with that +gun, and the effect varies widely. Lets go over those. + +* 1/5 chance - Miss. - Person misses. But at least they're confirmed safe, as +wolves and traitors don't get to have guns. + +* 1/5 chance - Unclean gun. - Gun explodes, in turn killing the shooter. Bummer! + +* 3/5 chance - Hit. - If it hits a wolf, they die instantly (and are revealed + to be a wolf.). If it hits a villager, 4/5 chance it'll just wound them and + make them unable to vote for the day. 1/5 chance it kills them. Oops! + +So, obviously no matter what the case might be, the gun holder will always +reveal the innocence of at least someone. + +=item Seer + +The seer is like a villager, but gets a special ability, the ability of +foresight (No, not Foresight Linux). + +They get to have a single vision every night, via the "wolf see " command, +in the vision, they will be told the exact role of the target. + +Although, in case I wasn't clear above: The seer will never be shown the +traitor, when SEE'ing the traitor, the seer will be told they are a "villager". + +During the day, the seer must share his findings with his fellow villagers +while concealing his identity as the seer because otherwise, he's as good as +dead the next night. + +It is common for the seer to form a "trust chain", a channel with all confirmed +safe users. Trust chains typically can make the biggest difference in who wins. + +Regardless of player count, there is always one seer. + +=item Harlot + +ATTENTION: Harlot is an old English word for prostitute, thus harlots might be +considered NSFW and can be disabled with werewolf:rated-g. See CONFIGURATION. + +By default: enabled. + +Harlots are similar to the seer. They can spend the night with one person per +round. They select their partner with "wolf visit ". Their partner is +immediately notified of their visit and their identity as harlot. + +But in the morning is when the real information becomes available. + +If the harlot visited a wolf, the bot will announce their identity as the harlot +and declare them dead. Since they are immediately removed from the game, finding +a wolf is only useful if they used a trust chain to reveal who they were +visiting before morning came. + +If the harlot visited the victim of a wolf, the same result happens, except +this time their death was worthless because they died indirectly through the +victim, not directly through a wolf. + +If none of these are the case, the harlot knows their partner is innocent and +vice versa. This may lead to a trust chain. + +Oh, and harlots have a trick, wolves cannot directly kill the harlot if they're +spending the night with someone else. The bot will announce the harlot was targeted +but was not home in the morning, no nicks are mentioned in this case. + +This is very good protection. Combined with the seer, they can, via creating a +trust chain, almost completely guarantee a win for the villagers. + +A single harlot is assigned to a game if it has six or more players. + +=item Guardian Angel + +NOTE: Some people think that guardian angels make the game less interesting. +Thus, by default they are enabled and can be disabled with werewolf:no-angels. +See CONFIGURATION. + +Guardian angels are like a weapon for the villagers, particularly the seer and +detective. + +Every night, the angel can guard one person with "wolf guard ", the +effect depends on a single factor. The person they're guarding is notified +of their presence, but not of the angel's nickname. + +In the morning... + +If they guarded the victim of the wolves, the bot announces that the guardian +angel saved the victim from the wolves. No nicks are mentioned and the victim +survives. + +If they guarded a wolf, the bot does a 50/50 on whether the angel escaped in +time or not. If they didn't, their identity is revealed and they are declared +dead. Like the harlot, this is only useful if they first revealed who they were +guarding to a trust chain before morning. + +If they escaped, there are no further messages. + +Of course, with a trust chain, the guardian angel can protect the seer or +detective from death. Thus, the wolves must kill the angel as soon as possible. + +A single guardian angel is assigned to a game if it has nine or more players. + +=item Traitor + +NOTE: Traitors can sometimes make it nearly impossible for the villagers to win, +thus they are disabled by default and can be enabled with werewolf:traitors. +See CONFIGURATION. + +Traitors are sneaky ones. They are EXACTLY like villagers, with only a few +minor differences. + +Biggest one is they're on the side of the werewolves. They only win if the +wolves do. (TODO: Needs some perfection.) + +Second biggest is every night they're given a list of players, with the wolves +indicated by (wolf). They are able to use the werewolf relay as well. + +Traitors always appear to be a villager, except to wolves, upon death or to the +detective. + +Not even the seer or harlot can detect they're a traitor. They might even +invite them into the trust chain! + +However, no need to fear, the detective, is here. The detective is able to +detect traitors. Hooray! + +But, BEWARE! TRAITORS ARE VERY DANGEROUS. VILLAGERS MUST IDENTIFY AND KILL THEM +AS SOON AS POSSIBLE. + +A single traitor is assigned to a game if it has twelve or more players. + +=item Detective + +NOTE: Some people think that detectives make the game less interesting, or even +unfair if traitors are disabled. Thus they are disabled by default and can be +enabled with werewolf:detectives. See CONFIGURATION. + +Detectives are EXACTLY like seers, with only three differences: + +1) They use their ability ("wolf id ") during the day, not the night. + +2) They can identify traitors. + +But, with such abilities, comes a great and risky price. Every time they use +the ID command, the bot does a 2/5 chance of their identity being revealed +to all the wolves and traitors. + +That's right, 2/5 chance. Identity revealed. To all wolves and traitors. + +The primary duty of the detective is to work together with the seer/harlot +(trust chain?) but focus on identifying the traitor as soon as possible. + +This is because, luck will run out. Eventually it is only logical to assume +they will be revealed to the villains. + +So indeed, detectives have a stressful job, and must be good at it, for the +sake of all that is good! + +A single detective is assigned to a game if it has sixteen or more players. + +=back + +=head1 CONFIGURATION + +One of this module's key features is how easily customizable it is. + +All your configuration options will go in the werewolf { } block in your +configuration file. Lets go over them now. + +--- + + chan "Server/#channel"; + +chan lets you "jail" Werewolf to a single IRC channel. Useful for an IRC bot +that serves multiple channels and doesn't want all channels playing it. + +It should be set to "/#". Yes, the slash is +required. Also, it is case-sensitive. + +--- + + rated-g 1; + +*DEFINING* this configuration option will disable harlots (A.K.A. prostitutes), +because you might consider them to be inappropriate for your target audience. + +NOTE: Defining it disables harlots, regardless of the actual value. Remove it +to re-enable harlots. + +--- + + no-angels 1; + +Same as rated-g. Although there are different reasons as to why you might wish +to disable guardian angels, so see ROLES for those. + +--- + + traitors 1; + +Same as above. Defining this option enables traitors. See ROLES for reasons why +and why you shouldn't enable traitors. + +--- + + detectives 1; + +Same as above. Defining this option enables detectives. See ROLES for reasons +why and why you shouldn't enable detectives. + +--- + + always-m 1; + +Again, merely defining this option enables it. always-m is intended for channels +like #defocus on Freenode that always stay +m. This is usually undesirable, so +probably a good idea to leave it out. + +--- + + no-wolfrelay 1; + +Same as above. This option disables the werewolf private relay feature. However, +at this time, wolves and traitors will still be told they can use the service. + +TODO: Fix this. + +You may desire to disable the relay feature to prevent from the bot lagging. +Although most modern networks should not give any issue. Let us know if they do. + +--- + + chghost-quit "Message"; + +This is useful as it causes the module to "ignore" disconnects by players with +reason "Message". It is intended to prevent from a plethora of issues caused by +disconnect/reconnect cycling on host mask change. + +WE HIGHLY RECOMMEND USING THIS OPTION. + +Usually, you'll want it set to: + + chghost-quit "Changing host"; + +But, check with your network's administrators to confirm. + +--- + +Also, for your convenience, we've placed a COMMANDS hash near the top of this +source file that you /should/ modify if using aliases, to avoid user confusion. + +We think it's pretty clear what you need to modify. Enjoy. + +=head1 SUPPORT, SUGGESTIONS, ETC. + +If you have any questions or generally need help. Best method is to join our +IRC channel. (See the README) + +Feature suggestions and bug reports should be posted to the B/F Tracker at +http://rm.xelhua.org. + +Due to the great complexity of this module, it would not make sense for it to +be completely bug-free. So, of course, testing and bug reports are very +appreciated. + +Thanks for assisting by testing! + +You can also contact Elijah directly at elijah@starcoder.info or "starcoder" on +IRC, if need be. + +=head1 AUTHOR + +This module was written by Elijah Perrault. + +Random Fact: Took five days to make. + +This module is maintained by Xelhua Development Group. + +=head1 LICENSE AND COPYRIGHT + +This module is Copyright (C) 2010-2011, Xelhua Development Group. + +This module is released under the same terms as Auto itself. + +=cut + +# vim: set ai et ts=4 sw=4: