6 no if $] > 5.017011, warnings
=> 'experimental::smartmatch';
7 use parent qw
/Exporter/;
10 use Storable qw
/dclone/;
12 our $VERSION = '0.001001';
14 sub defconst
{ constant
->import($_ => $_) for @_ }
18 defconst qw
/vanilla goon doctor vigilante roleblocker jailkeeper gunsmith tracker watcher bodyguard rolecop cop sk hider/;
21 defconst qw
/mafia town/;
24 defconst qw
/miller godfather weak macho bulletproof/;
27 defconst qw
/MSG_NIGHT MSG_DAY MSG_PLAYERS_ALIVE MSG_DEATH MSG_GUNCHECK MSG_NORESULT MSG_TRACK MSG_WATCH MSG_COP MSG_ROLECOP/;
30 defconst qw
/ACT_KILL ACT_LYNCH ACT_PROTECT ACT_GUARD ACT_ROLEBLOCK ACT_GUNCHECK ACT_TRACK ACT_WATCH ACT_ROLECOP ACT_COP ACT_TRACK_RESULT ACT_WATCH_RESULT ACT_HIDE/;
35 ROLE
=> [vanilla
, goon
, doctor
, vigilante
, roleblocker
, jailkeeper
, gunsmith
, tracker
, watcher
, bodyguard
, rolecop
, cop
, sk
, hider
],
36 FACTION
=> [mafia
, town
],
37 FLAG
=> [miller
, godfather
, weak
, macho
, bulletproof
],
38 ACTION_ORDER
=> [ACT_HIDE
, ACT_ROLEBLOCK
, ACT_PROTECT
, ACT_GUARD
, ACT_GUNCHECK
, ACT_ROLECOP
, ACT_COP
, ACT_TRACK
, ACT_WATCH
, ACT_KILL
, ACT_LYNCH
, ACT_TRACK_RESULT
, ACT_WATCH_RESULT
],
39 INVESTIGATIVE_ACTIONS
=> [ACT_GUNCHECK
, ACT_TRACK
, ACT_WATCH
, ACT_ROLECOP
, ACT_COP
],
40 GUNROLES
=> [vigilante
, gunsmith
],
45 grep { $_ !~ [qw
/import/] and exists &$_ } keys %{__PACKAGE__
. '::'};
48 ################################################## Helper subs
52 goto &Exporter
::import
;
55 my (%players, %tplayers, @actions);
72 my %hash = map { $_ => 1 } @_;
77 return "Day $daycnt" if $isday;
78 return "Night $nightcnt" unless $isday;
82 my %player = %{$players{$_[0]}};
83 my ($faction, $role) = ($player{faction
}, $player{role
});
84 if (defined $faction && $faction eq town
&& $role eq vanilla
) {
86 $role = 'Vanilla Townie';
89 push @tokens, ucfirst $faction if $faction;
90 for my $flag (@
{FLAG
()}) {
91 push @tokens, ucfirst $flag if $player{$flag}
93 push @tokens, ucfirst $role unless $role eq goon
&& $player{godfather
};
98 my ($type, @args) = @_;
102 say '' unless $first;
104 say "It is Night $night";
109 say '' unless $first;
111 say "It is Day $day";
114 when (MSG_PLAYERS_ALIVE
) {
116 say "Players alive: ", join ', ', @args
121 my ($who, $reason) = @args{'target', 'reason'};
123 my $rolename = rolename
$who;
124 say "$who ($rolename) — $reason $phase";
127 when (MSG_GUNCHECK
) {
129 my ($gunsmith, $who, $hasgun) = @args{'source', 'target', 'result'};
130 say "$gunsmith: $who has a gun" if $hasgun;
131 say "$gunsmith: $who does not have a gun" unless $hasgun;
134 when (MSG_NORESULT
) {
136 my ($who) = $args{'source'};
137 say "$who: No result"
142 my ($tracker, $who, $result) = @args{'source', 'target', 'result'};
143 my @result = @
{$result};
145 say "$tracker: $who did not visit anyone" unless scalar @result;
146 say "$tracker: $who visited: @result" if scalar @result;
151 my ($watcher, $who, $result) = @args{'source', 'target', 'result'};
152 my @result = @
{$result};
154 say "$watcher: $who was not visited by anyone" unless scalar @result;
155 say "$watcher: $who was visited by: @result" if scalar @result;
160 my ($rolecop, $who, $role) = @args{'source', 'target', 'result'};
161 say "$rolecop: $who\'s role is: $role"
166 my ($cop, $who, $ismafia) = @args{'source', 'target', 'result'};
167 say "$cop: $who is mafia" if $ismafia;
168 say "$cop: $who is not mafia" unless $ismafia;
174 my ($delay, $type, %args) = @_;
175 $actions[$delay]->{$type} //= [];
176 if (exists $args{target
} && exists $args{source
} && $players{$args{target
}}{faction
} eq mafia
&& $players{$args{source
}}{weak
}) {
177 putaction
($delay, ACT_KILL
, target
=> $args{source
}, reason
=> 'targeted scum');
179 push $actions[$delay]->{$type}, \
%args
183 my ($type, $args) = @_;
185 my $source = $args{source
};
186 my $target = $args{target
};
187 if (defined $source && defined $target) {
188 # Watcher and tracker variables
189 $tplayers{$source}{targets
} //= [];
190 push $tplayers{$source}{targets
}, $target;
191 $tplayers{$target}{sources
} //= [];
192 push $tplayers{$target}{sources
}, $source;
194 # Copy this action to everybody hiding behind $target
195 if (exists $tplayers{$target}{hiders
}) {
196 for my $target (@
{$tplayers{$target}{hiders
}}) {
198 $args{target
} = $target;
199 $args{hidepierce
} = 1;
200 doaction
($type, \
%args);
204 # Check if the action should be blocked
205 my $strongkill = $type eq ACT_KILL
&& $args{strong
};
206 my $roleblocked = $tplayers{$source}{roleblocked
};
207 my $hidden = $tplayers{$target}{hidden
};
208 my $hidepierce = $args{hidepierce
};
209 if ($source && (( $roleblocked && !$strongkill ) || ($hidden && !$hidepierce) )) {
210 msg MSG_NORESULT
, %args if $type ~~ INVESTIGATIVE_ACTIONS
;
218 break if $tplayers{$target}{bulletproof
} && defined $source;
219 if ($tplayers{$target}{guard_count
} && defined $source) {
220 $tplayers{$target}{guard_count
}--;
221 # Copy this action to the first guard
222 $args{target
} = shift $tplayers{$target}{guards
};
226 if ($tplayers{$target}{protection
} && !$args{strong
}) {
227 $tplayers{$target}{protection
}--;
230 msg MSG_DEATH
, %args;
231 delete $players{$target}
235 if ($tplayers{$target}{guard_count
}) {
236 $tplayers{$target}{guard_count
}--;
237 $args{target
} = shift $tplayers{$target}{guards
};
238 $target=$args{target
};
240 if ($tplayers{$target}{protection
}) {
241 $tplayers{$target}{protection
}--;
244 msg MSG_DEATH
, %args, reason
=> 'lynched';
245 delete $players{$target}
249 my $count = $args{count
} // 1;
250 $tplayers{$target}{protection
} += $count unless $tplayers{$target}{macho
}
254 $tplayers{$target}{roleblocked
} = 1
258 my $role = $players{$target}{role
};
259 my $hasgun = $role ~~ GUNROLES
|| ($players{$target}{faction
} eq mafia
&& $role ne doctor
);
260 msg MSG_GUNCHECK
, %args, result
=> $hasgun
263 when(ACT_TRACK_RESULT
){
264 msg MSG_TRACK
, %args, result
=> [ uniq @
{$tplayers{$target}{targets
} // []} ];
267 when(ACT_WATCH_RESULT
){
268 msg MSG_WATCH
, %args, result
=> [ uniq @
{$tplayers{$target}{sources
} // []} ];
272 $tplayers{$target}{guard_count
}++;
273 $tplayers{$target}{guards
} //= [];
274 push $tplayers{$target}{guards
}, $source;
278 my $result = $players{$target}{role
};
279 $result = vanilla
if $result eq goon
;
280 msg MSG_ROLECOP
, %args, result
=> ucfirst $result
284 my $result = $players{$target}{faction
} eq mafia
;
285 $result = 1 if $players{$target}{miller
};
286 $result = 0 if $players{$target}{godfather
};
287 msg MSG_COP
, %args, result
=> $result
291 $tplayers{$source}{hidden
} = 1;
292 $tplayers{$target}{hiders
} //= [];
293 push $tplayers{$target}{hiders
}, $source
298 sub process_phase_change
{
299 %tplayers = %{dclone \
%players};
300 my $actions = shift @actions;
301 for my $type (@
{ACTION_ORDER
()}) {
302 doaction
$type, $_ for @
{$actions->{$type}}
306 ################################################## User subs
309 my ($name, @args) = @_;
311 for my $trait (@args) {
313 $player{role
} = $trait when ROLE
;
314 $player{faction
} = $trait when FACTION
;
315 $player{$trait} = 1 when FLAG
;
319 $players{$name} = \
%player;
323 process_phase_change
;
325 msg MSG_DAY
, ++$daycnt;
326 msg MSG_PLAYERS_ALIVE
, keys %players;
330 process_phase_change
;
332 msg MSG_NIGHT
, ++$nightcnt;
333 msg MSG_PLAYERS_ALIVE
, keys %players;
338 putaction
0, ACT_LYNCH
, target
=> $who;
342 my ($killer, $who, $reason, @args) = @_;
343 putaction
0, ACT_KILL
, target
=> $who, source
=> $killer, reason
=> $reason, @args;
347 my ($doctor, $who) = @_;
348 putaction
0, ACT_PROTECT
, target
=> $who, source
=> $doctor;
352 my ($vig, $who, $reason, @args) = @_;
353 putaction
0, ACT_KILL
, target
=> $who, source
=> $vig, reason
=> $reason, @args;
357 my ($roleblocker, $who) = @_;
358 putaction
0, ACT_ROLEBLOCK
, target
=> $who, source
=> $roleblocker;
362 my ($jailkeeper, $who) = @_;
363 putaction
0, ACT_ROLEBLOCK
, target
=> $who, source
=> $jailkeeper;
364 putaction
0, ACT_PROTECT
, target
=> $who, source
=> $jailkeeper, count
=> 1000;
368 my ($gunsmith, $who) = @_;
369 putaction
0, ACT_GUNCHECK
, target
=> $who, source
=> $gunsmith;
373 my ($tracker, $who) = @_;
374 putaction
0, ACT_TRACK
, target
=> $who, source
=> $tracker;
375 putaction
0, ACT_TRACK_RESULT
, target
=> $who, source
=> $tracker;
379 my ($watcher, $who) = @_;
380 putaction
0, ACT_WATCH
, target
=> $who, source
=> $watcher;
381 putaction
0, ACT_WATCH_RESULT
, target
=> $who, source
=> $watcher;
385 my ($guard, $who) = @_;
386 putaction
0, ACT_GUARD
, target
=> $who, source
=> $guard;
390 my ($rolecop, $who) = @_;
391 putaction
0, ACT_ROLECOP
, target
=> $who, source
=> $rolecop;
395 my ($cop, $who) = @_;
396 putaction
0, ACT_COP
, target
=> $who, source
=> $cop;
400 my ($sk, $who, $reason, @args) = @_;
401 putaction
0, ACT_KILL
, target
=> $who, source
=> $sk, reason
=> $reason, @args;
405 my ($hider, $who) = @_;
406 putaction
0, ACT_HIDE
, target
=> $who, source
=> $hider;
416 Mafia - easily moderate Mafia games
423 player 'Banana Bob', cop, town;
424 player 'Dragon Phoenix', vanilla, townie;
425 player 'Gammie', mafia, goon;
426 player 'gslamm', vanilla, townie;
427 player 'Untrod Tripod', mafia, goon;
428 player 'Werebear', vanilla, townie;
429 player 'willows_weep', town, doctor;
432 lynch 'Untrod Tripod';
435 factionkill 'Gammie', 'willows_weep', 'shot';
436 copcheck 'Banana Bob', 'gslamm';
445 Mafia.pm is a Perl extension for easily moderating Mafia games. You don't even need to know Perl to use it (see L<"WHAT YOU NEED TO KNOW"> for details).
447 =head1 WHAT YOU NEED TO KNOW
449 A typical script starts with the following two lines
454 The rest of the script is a series of function calls that describe the players and their actions.
456 A function call looks like this:
458 function_name first_argument, second_argument, ...
460 Each argument is either a number, a string (which is a sequence of characters between single or double quotes, such as C<'badguy'>, C<'qwrf'>) or a constant (such as C<mafia>, C<vanilla>, C<bulletproof>).
464 player 'Somebody', mafia, goon; # player is the function, 'Somebody' is a string, mafia and goon are constants.
465 lynch 'Nobody'; # lynch is the function, 'Nobody' is a string.
466 day; # day is the function. There are no arguments.
472 =item B<player> I<name>, I<trait>, ...
474 Defines a new player named I<name> and its traits (role, faction, role modifiers).
476 Roles: C<vanilla, goon, doctor, vigilante, roleblocker, jailkeeper, gunsmith, tracker, watcher, bodyguard, rolecop, cop, sk, hider>.
478 Factions: C<mafia, town>. C<townie> is a synonim for C<town>.
480 Other attributes: C<miller, godfather, weak, macho, bulletproof>
482 These traits may be specified in any order.
486 player 'alice', town, bulletproof, miller, vigilante; # Alice is a NK-Immune Miller Vig
487 player 'bob', town, weak, doctor; # Bob is a Town Weak Doctor
488 player 'eve', mafia, godfather, goon; # Eve is a Mafia Godfather
492 Defines the start of a new Day. All actions in the previous Night are now resolved.
496 Defines the start of a new Night. All actions in the previous Day are now resolved.
498 =item B<lynch> I<player>
500 Notes that I<player> was lynched.
502 =item B<factionkill> I<killer>, I<player>, I<flavour>, [ strong => 1 ]
504 Notes that I<killer> killed I<player> with flavour I<flavour>. Append C<< strong => 1 >> if the kill should ignore roleblocks and doctor/jailkeeper protections. Use this for mafia kills.
508 factionkill 'eve', 'alice', 'strangled to death';
509 factionkill 'eve', 'bob', 'brutally murdered', strong => 1; # This is a strongman kill
511 =item B<protect> I<doctor>, I<player>
513 Notes that I<doctor> protected I<player>.
515 =item B<vig> I<vigilante>, I<player>, I<flavour>, [ strong => 1 ]
517 Notes that I<killer> killed I<player> with flavour I<flavour>. Append C<< strong => 1 >> if the kill should ignore roleblocks and doctor/jailkeeper protections. Use this for Vigilante/Juggernaut kills.
521 vig 'chuck', 'bob', 'shot';
522 vig 'chuck', 'bob', 'shot seven times', strong => 1; # This is a Juggernaut (Strongman Vigilante) kill
524 =item B<roleblock> I<roleblocker>, I<player>
526 Notes that I<roleblocker> roleblocked I<player>.
528 =item B<jailkeep> I<jailkeeper>, I<player>
530 Notes that I<jailkeeper> roleblocked and protected I<player>.
532 =item B<guncheck> I<gunsmith>, I<player>
534 Notes that I<gunsmith> checked if I<player> has a gun.
536 =item B<track> I<tracker>, I<player>
538 Notes that I<tracker> tracked I<player>.
540 =item B<watch> I<watcher>, I<player>
542 Notes that I<watcher> watched I<player>.
544 =item B<guard> I<bodyguard>, I<player>
546 Notes that I<bodyguard> guarded I<player>
548 =item B<rolecopcheck> I<rolecop>, I<player>
550 Notes that I<rolecop> checked the role of I<player>
552 =item B<copcheck> I<cop>, I<player>
554 Notes that I<cop> checked whether I<player> is mafia.
556 =item B<skill> I<SK>, I<player>, I<flavour>, [ strong => 1 ]
558 Notes that I<SK> killed player with flavour I<flavour>. Append C<< strong => 1 >>> if the kill should ignore roleblocks and doctor/jailkeeper protections. Use this for Serial Killer kills.
560 =item B<hide> I<hider>, I<player>
562 Notes that I<hider> hid behind I<player>.
568 Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
570 =head1 COPYRIGHT AND LICENSE
572 Copyright (C) 2013 by Marius Gavrilescu
574 This library is free software; you can redistribute it and/or modify
575 it under the same terms as Perl itself, either Perl version 5.14.2 or,
576 at your option, any later version of Perl 5 you may have available.
This page took 0.049999 seconds and 4 git commands to generate.