]>
iEval git - mafia.git/blob - lib/Mafia.pm
6 use parent qw
/Exporter/ ;
9 use Storable qw
/dclone/ ;
11 our $VERSION = '0.001003' ;
13 sub defconst
{ constant
-> import ( $_ => $_ ) for @_ }
17 defconst qw
/vanilla goon doctor vigilante roleblocker jailkeeper gunsmith tracker watcher bodyguard rolecop cop sk hider/ ;
20 defconst qw
/mafia town/ ;
23 defconst qw
/miller godfather weak macho bulletproof/ ;
26 defconst qw
/MSG_NIGHT MSG_DAY MSG_PLAYERS_ALIVE MSG_DEATH MSG_GUNCHECK MSG_NORESULT MSG_TRACK MSG_WATCH MSG_COP MSG_ROLECOP/ ;
29 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/ ;
32 use constant
+{ ## no critic (Capitalization)
34 ROLE
=> [ vanilla
, goon
, doctor
, vigilante
, roleblocker
, jailkeeper
, gunsmith
, tracker
, watcher
, bodyguard
, rolecop
, cop
, sk
, hider
],
35 FACTION
=> [ mafia
, town
],
36 FLAG
=> [ miller
, godfather
, weak
, macho
, bulletproof
],
37 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
],
38 INVESTIGATIVE_ACTIONS
=> [ ACT_GUNCHECK
, ACT_TRACK
, ACT_WATCH
, ACT_ROLECOP
, ACT_COP
],
39 GUNROLES
=> [ vigilante
, gunsmith
],
42 my %ROLE_HASH = map { $_ => 1 } @
{ ROLE
()};
43 my %FACTION_HASH = map { $_ => 1 } @
{ FACTION
()};
44 my %FLAG_HASH = map { $_ => 1 } @
{ FLAG
()};
45 my %INVESTIGATIVE_ACTIONS_HASH = map { $_ => 1 } @
{ INVESTIGATIVE_ACTIONS
()};
46 my %GUNROLES_HASH = map { $_ => 1 } @
{ GUNROLES
()};
49 no strict
'refs' ; ## no critic (ProhibitNoStrict)
50 grep { $_ !~ [ qw
/import/ ] and exists & $_ } keys %{ __PACKAGE__
. '::' };
53 ################################################## Helper subs
57 goto & Exporter
:: import
;
60 my ( %players , %tplayers , @actions );
77 my %hash = map { $_ => 1 } @_ ;
82 return "Day $daycnt " if $isday ;
83 return "Night $nightcnt " unless $isday ;
86 sub rolename
{ ## no critic (RequireArgUnpacking)
87 my %player = %{ $players { $_ [ 0 ]}};
88 my ( $faction , $role ) = ( $player { faction
}, $player { role
});
89 if ( defined $faction && $faction eq town
&& $role eq vanilla
) {
91 $role = 'Vanilla Townie' ;
94 push @tokens , ucfirst $faction if $faction ;
95 for my $flag ( @
{ FLAG
()}) {
96 push @tokens , ucfirst $flag if $player { $flag }
98 push @tokens , ucfirst $role unless $role eq goon
&& $player { godfather
};
103 my ( $type , @args ) = @_ ;
107 say '' unless $first ;
109 say "It is Night $night " ;
114 say '' unless $first ;
116 say "It is Day $day " ;
119 MSG_PLAYERS_ALIVE
=> sub {
121 say 'Players alive: ' , join ', ' , @args
126 my ( $who , $reason ) = @args { 'target' , 'reason' };
128 my $rolename = rolename
$who ;
129 say " $who ( $rolename ) — $reason $phase " ;
132 MSG_GUNCHECK
=> sub {
134 my ( $gunsmith , $who , $hasgun ) = @args { 'source' , 'target' , 'result' };
135 say " $gunsmith : $who has a gun" if $hasgun ;
136 say " $gunsmith : $who does not have a gun" unless $hasgun ;
139 MSG_NORESULT
=> sub {
141 my ( $who ) = $args { 'source' };
142 say " $who : No result"
147 my ( $tracker , $who , $result ) = @args { 'source' , 'target' , 'result' };
148 my @result = @
{ $result };
150 say " $tracker : $who did not visit anyone" unless scalar @result ;
151 say " $tracker : $who visited: @result " if scalar @result ;
156 my ( $watcher , $who , $result ) = @args { 'source' , 'target' , 'result' };
157 my @result = @
{ $result };
159 say " $watcher : $who was not visited by anyone" unless scalar @result ;
160 say " $watcher : $who was visited by: @result " if scalar @result ;
165 my ( $rolecop , $who , $role ) = @args { 'source' , 'target' , 'result' };
166 say " $rolecop : $who \' s role is: $role "
171 my ( $cop , $who , $ismafia ) = @args { 'source' , 'target' , 'result' };
172 say " $cop : $who is mafia" if $ismafia ;
173 say " $cop : $who is not mafia" unless $ismafia ;
181 my ( $delay , $type , %args ) = @_ ;
182 $actions [ $delay ]->{ $type } // = [];
183 if ( exists $args { target
} && exists $args { source
} && $players { $args { target
}}{ faction
} eq mafia
&& $players { $args { source
}}{ weak
}) {
184 putaction
( $delay , ACT_KILL
, target
=> $args { source
}, reason
=> 'targeted scum' );
186 push @
{ $actions [ $delay ]->{ $type }}, \
%args
189 sub doaction
{ ## no critic (ProhibitExcessComplexity)
190 my ( $type , $args ) = @_ ;
192 my $source = $args { source
};
193 my $target = $args { target
};
194 if ( defined $source && defined $target ) {
195 # Watcher and tracker variables
196 $tplayers { $source }{ targets
} // = [];
197 push @
{ $tplayers { $source }{ targets
}}, $target ;
198 $tplayers { $target }{ sources
} // = [];
199 push @
{ $tplayers { $target }{ sources
}}, $source ;
201 # Copy this action to everybody hiding behind $target
202 if ( exists $tplayers { $target }{ hiders
}) {
203 for my $target ( @
{ $tplayers { $target }{ hiders
}}) {
204 my %new_args = %args ;
205 $new_args { target
} = $target ;
206 $new_args { hidepierce
} = 1 ;
207 doaction
( $type , \
%new_args );
211 # Check if the action should be blocked
212 my $strongkill = $type eq ACT_KILL
&& $args { strong
};
213 my $roleblocked = $tplayers { $source }{ roleblocked
};
214 my $hidden = $tplayers { $target }{ hidden
};
215 my $hidepierce = $args { hidepierce
};
216 if ( $source && (( $roleblocked && ! $strongkill ) || ( $hidden && ! $hidepierce ) )) {
217 msg MSG_NORESULT
, %args if $INVESTIGATIVE_ACTIONS_HASH { $type };
224 break if $tplayers { $target }{ bulletproof
} && defined $source ;
225 if ( $tplayers { $target }{ guard_count
} && defined $source ) {
226 $tplayers { $target }{ guard_count
}--;
227 # Copy this action to the first guard
228 $args { target
} = shift @
{ $tplayers { $target }{ guards
}};
232 if ( $tplayers { $target }{ protection
} && ! $args { strong
}) {
233 $tplayers { $target }{ protection
}--;
236 msg MSG_DEATH
, %args ;
237 delete $players { $target }
241 if ( $tplayers { $target }{ guard_count
}) {
242 $tplayers { $target }{ guard_count
}--;
243 $args { target
} = shift @
{ $tplayers { $target }{ guards
}};
244 $target = $args { target
};
246 if ( $tplayers { $target }{ protection
}) {
247 $tplayers { $target }{ protection
}--;
250 msg MSG_DEATH
, %args , reason
=> 'lynched' ;
251 delete $players { $target }
255 my $count = $args { count
} // 1 ;
256 $tplayers { $target }{ protection
} += $count unless $tplayers { $target }{ macho
}
259 ACT_ROLEBLOCK
=> sub {
260 $tplayers { $target }{ roleblocked
} = 1
263 ACT_GUNCHECK
=> sub {
264 my $role = $players { $target }{ role
};
265 my $hasgun = $GUNROLES_HASH { $role } || ( $players { $target }{ faction
} eq mafia
&& $role ne doctor
);
266 msg MSG_GUNCHECK
, %args , result
=> $hasgun
269 ACT_TRACK_RESULT
=> sub {
270 msg MSG_TRACK
, %args , result
=> [ uniq @
{ $tplayers { $target }{ targets
} // []} ];
273 ACT_WATCH_RESULT
=> sub {
274 msg MSG_WATCH
, %args , result
=> [ uniq @
{ $tplayers { $target }{ sources
} // []} ];
278 $tplayers { $target }{ guard_count
}++;
279 $tplayers { $target }{ guards
} // = [];
280 push @
{ $tplayers { $target }{ guards
}}, $source ;
284 my $result = $players { $target }{ role
};
285 $result = vanilla
if $result eq goon
;
286 msg MSG_ROLECOP
, %args , result
=> ucfirst $result
290 my $result = $players { $target }{ faction
} eq mafia
;
291 $result = 1 if $players { $target }{ miller
};
292 $result = 0 if $players { $target }{ godfather
};
293 msg MSG_COP
, %args , result
=> $result
297 $tplayers { $source }{ hidden
} = 1 ;
298 $tplayers { $target }{ hiders
} // = [];
299 push @
{ $tplayers { $target }{ hiders
}}, $source
306 sub process_phase_change
{
307 %tplayers = %{ dclone \
%players };
308 my $actions = shift @actions ;
309 for my $type ( @
{ ACTION_ORDER
()}) {
310 doaction
$type , $_ for @
{ $actions ->{ $type }}
314 ################################################## User subs
317 my ( $name , @args ) = @_ ;
319 for my $trait ( @args ) {
320 $player { role
} = $trait if $ROLE_HASH { $trait };
321 $player { faction
} = $trait if $FACTION_HASH { $trait };
322 $player { $trait } = 1 if $FLAG_HASH { $trait };
325 $players { $name } = \
%player ;
329 process_phase_change
;
331 msg MSG_DAY
, ++ $daycnt ;
332 msg MSG_PLAYERS_ALIVE
, keys %players ;
336 process_phase_change
;
338 msg MSG_NIGHT
, ++ $nightcnt ;
339 msg MSG_PLAYERS_ALIVE
, keys %players ;
344 putaction
0 , ACT_LYNCH
, target
=> $who ;
348 my ( $killer , $who , $reason , @args ) = @_ ;
349 putaction
0 , ACT_KILL
, target
=> $who , source
=> $killer , reason
=> $reason , @args ;
353 my ( $doctor , $who ) = @_ ;
354 putaction
0 , ACT_PROTECT
, target
=> $who , source
=> $doctor ;
358 my ( $vig , $who , $reason , @args ) = @_ ;
359 putaction
0 , ACT_KILL
, target
=> $who , source
=> $vig , reason
=> $reason , @args ;
363 my ( $roleblocker , $who ) = @_ ;
364 putaction
0 , ACT_ROLEBLOCK
, target
=> $who , source
=> $roleblocker ;
368 my ( $jailkeeper , $who ) = @_ ;
369 putaction
0 , ACT_ROLEBLOCK
, target
=> $who , source
=> $jailkeeper ;
370 putaction
0 , ACT_PROTECT
, target
=> $who , source
=> $jailkeeper , count
=> 1000 ;
374 my ( $gunsmith , $who ) = @_ ;
375 putaction
0 , ACT_GUNCHECK
, target
=> $who , source
=> $gunsmith ;
379 my ( $tracker , $who ) = @_ ;
380 putaction
0 , ACT_TRACK
, target
=> $who , source
=> $tracker ;
381 putaction
0 , ACT_TRACK_RESULT
, target
=> $who , source
=> $tracker ;
385 my ( $watcher , $who ) = @_ ;
386 putaction
0 , ACT_WATCH
, target
=> $who , source
=> $watcher ;
387 putaction
0 , ACT_WATCH_RESULT
, target
=> $who , source
=> $watcher ;
391 my ( $guard , $who ) = @_ ;
392 putaction
0 , ACT_GUARD
, target
=> $who , source
=> $guard ;
396 my ( $rolecop , $who ) = @_ ;
397 putaction
0 , ACT_ROLECOP
, target
=> $who , source
=> $rolecop ;
401 my ( $cop , $who ) = @_ ;
402 putaction
0 , ACT_COP
, target
=> $who , source
=> $cop ;
406 my ( $sk , $who , $reason , @args ) = @_ ;
407 putaction
0 , ACT_KILL
, target
=> $who , source
=> $sk , reason
=> $reason , @args ;
411 my ( $hider , $who ) = @_ ;
412 putaction
0 , ACT_HIDE
, target
=> $who , source
=> $hider ;
422 Mafia - easily moderate Mafia games
429 player 'Banana Bob', cop, town;
430 player 'Dragon Phoenix', vanilla, townie;
431 player 'Gammie', mafia, goon;
432 player 'gslamm', vanilla, townie;
433 player 'Untrod Tripod', mafia, goon;
434 player 'Werebear', vanilla, townie;
435 player 'willows_weep', town, doctor;
438 lynch 'Untrod Tripod';
441 factionkill 'Gammie', 'willows_weep', 'shot';
442 copcheck 'Banana Bob', 'gslamm';
451 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).
453 =head1 WHAT YOU NEED TO KNOW
455 A typical script starts with the following two lines
460 The rest of the script is a series of function calls that describe the players and their actions.
462 A function call looks like this:
464 function_name first_argument, second_argument, ...
466 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>).
470 player 'Somebody', mafia, goon; # player is the function, 'Somebody' is a string, mafia and goon are constants.
471 lynch 'Nobody'; # lynch is the function, 'Nobody' is a string.
472 day; # day is the function. There are no arguments.
478 =item B<player> I<name>, I<trait>, ...
480 Defines a new player named I<name> and its traits (role, faction, role modifiers).
482 Roles: C<vanilla, goon, doctor, vigilante, roleblocker, jailkeeper, gunsmith, tracker, watcher, bodyguard, rolecop, cop, sk, hider>.
484 Factions: C<mafia, town>. C<townie> is a synonim for C<town>.
486 Other attributes: C<miller, godfather, weak, macho, bulletproof>
488 These traits may be specified in any order.
492 player 'alice', town, bulletproof, miller, vigilante; # Alice is a NK-Immune Miller Vig
493 player 'bob', town, weak, doctor; # Bob is a Town Weak Doctor
494 player 'eve', mafia, godfather, goon; # Eve is a Mafia Godfather
498 Defines the start of a new Day. All actions in the previous Night are now resolved.
502 Defines the start of a new Night. All actions in the previous Day are now resolved.
504 =item B<lynch> I<player>
506 Notes that I<player> was lynched.
508 =item B<factionkill> I<killer>, I<player>, I<flavour>, [ strong => 1 ]
510 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.
514 factionkill 'eve', 'alice', 'strangled to death';
515 factionkill 'eve', 'bob', 'brutally murdered', strong => 1; # This is a strongman kill
517 =item B<protect> I<doctor>, I<player>
519 Notes that I<doctor> protected I<player>.
521 =item B<vig> I<vigilante>, I<player>, I<flavour>, [ strong => 1 ]
523 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.
527 vig 'chuck', 'bob', 'shot';
528 vig 'chuck', 'bob', 'shot seven times', strong => 1; # This is a Juggernaut (Strongman Vigilante) kill
530 =item B<roleblock> I<roleblocker>, I<player>
532 Notes that I<roleblocker> roleblocked I<player>.
534 =item B<jailkeep> I<jailkeeper>, I<player>
536 Notes that I<jailkeeper> roleblocked and protected I<player>.
538 =item B<guncheck> I<gunsmith>, I<player>
540 Notes that I<gunsmith> checked if I<player> has a gun.
542 =item B<track> I<tracker>, I<player>
544 Notes that I<tracker> tracked I<player>.
546 =item B<watch> I<watcher>, I<player>
548 Notes that I<watcher> watched I<player>.
550 =item B<guard> I<bodyguard>, I<player>
552 Notes that I<bodyguard> guarded I<player>
554 =item B<rolecopcheck> I<rolecop>, I<player>
556 Notes that I<rolecop> checked the role of I<player>
558 =item B<copcheck> I<cop>, I<player>
560 Notes that I<cop> checked whether I<player> is mafia.
562 =item B<skill> I<SK>, I<player>, I<flavour>, [ strong => 1 ]
564 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.
566 =item B<hide> I<hider>, I<player>
568 Notes that I<hider> hid behind I<player>.
574 Marius Gavrilescu, E<lt>marius@ieval.roE<gt>
576 =head1 COPYRIGHT AND LICENSE
578 Copyright (C) 2013-2017 by Marius Gavrilescu
580 This library is free software; you can redistribute it and/or modify
581 it under the same terms as Perl itself, either Perl version 5.14.2 or,
582 at your option, any later version of Perl 5 you may have available.
This page took 0.117722 seconds and 4 git commands to generate.