Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
labspace/labspace.tcl
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1152 lines (903 sloc)
28.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # LabSpace 1.0 | |
| # Copyright (C) 2011 Gunnar Beutner | |
| # | |
| # This program is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU General Public License | |
| # as published by the Free Software Foundation; either version 2 | |
| # of the License, or (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU General Public License for more details. | |
| # | |
| # You should have received a copy of the GNU General Public License | |
| # along with this program; if not, write to the Free Software | |
| # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| # TODO: | |
| # [ ] texts | |
| # [ ] highscores | |
| # [-] different weapons | |
| # [-] secret voting | |
| # [ ] list remaining players after each round (for irssi users) | |
| # [ ] document code | |
| # [ ] performance counters | |
| # [ ] advance game state if active scientist/investigator leaves the channel | |
| # [ ] remove "Done." notice for votes? | |
| # [ ] tweak sbnc's floodcontrol code | |
| # configuration options | |
| set ::ls_min_players 6 | |
| set ::ls_use_cmsg 1 | |
| set ::ls_debugmode 0 | |
| set ::ls_kill_messages { | |
| "%NICK% was brutally murdered." | |
| "%NICK% was vaporized by the scientist's death ray." | |
| "%NICK% slipped into a coma after drinking their poison-laced morning coffee." | |
| "%NICK% was crushed to death by a 5-ton boulder." | |
| "%NICK% couldn't escape from the scientist's killbot army." | |
| } | |
| set ::ls_event_handlers { | |
| ls_log_event_handler | |
| ls_stats_event_handler | |
| } | |
| if {![info exists ::ls_stats_events]} { | |
| set ::ls_stats_events [list] | |
| } | |
| bind pub - !labspace ls_pub_cmd_labspace | |
| bind pub - !nolabspace ls_pub_cmd_remove | |
| bind pub - !add ls_pub_cmd_add | |
| bind pub - !remove ls_pub_cmd_remove | |
| bind pub - !wait ls_pub_cmd_wait | |
| bind msg - kill ls_msg_cmd_kill | |
| bind notc - kill* ls_notc_cmd_kill | |
| bind msg - investigate ls_msg_cmd_investigate | |
| bind notc - investigate* ls_notc_cmd_investigate | |
| bind msg - vote ls_msg_cmd_vote | |
| bind notc - vote* ls_notc_cmd_vote | |
| bind msg n&- smite ls_msg_cmd_smite | |
| bind notc n&- smite* ls_notc_cmd_smite | |
| bind pubm - * ls_pubm_handler | |
| bind part - * ls_leave_handler | |
| bind sign - * ls_leave_handler | |
| bind kick - * ls_kick_handler | |
| bind nick - * ls_nick_handler | |
| internaltimer 10 1 ls_timer_advance_state [getctx] | |
| # work-around for a bug in sbnc's floodcontrol code | |
| internaltimer 1 1 ls_bug_timer | |
| proc ls_bug_timer {} {} | |
| # clear game state when the bot gets disconnected from IRC | |
| internalbind svrdisconnect ls_clear_state | |
| internaltimer 10 1 ls_flush_stats | |
| proc ls_clear_state {} { | |
| global ls_gamestate ls_gamestate_timeout ls_gamestate_delay | |
| array unset ls_gamestate | |
| array unset ls_gamestate_timeout | |
| array unset ls_gamestate_delay | |
| } | |
| # sends a debug message | |
| proc ls_debug {chan message} { | |
| global ls_debugmode | |
| if {$ls_debugmode} { | |
| ls_putmsg $chan "DEBUG: $message" | |
| } | |
| } | |
| proc ls_log_event_handler {channel name arguments} { | |
| set file [open "labspace.log" "a"] | |
| puts $file "[unixtime] channel: $channel, name: $name, args: \"[join $arguments {" "}]\"" | |
| close $file | |
| } | |
| proc ls_stats_event_handler {channel name arguments} { | |
| global ls_stats_events | |
| if {![info exists ls_stats_events]} { | |
| set ls_stats_events [list] | |
| } | |
| lappend ls_stats_events [list [clock seconds] $channel $name $arguments] | |
| } | |
| proc ls_flush_stats {} { | |
| global ls_stats_events | |
| if {![info exists ls_stats_events]} { | |
| return | |
| } | |
| setctx "stats" | |
| while {[llength $ls_stats_events] > 0} { | |
| set event [lindex $ls_stats_events 0] | |
| set timestamp [lindex $event 0] | |
| if {$timestamp > [expr {[clock seconds] - 600}]} { | |
| break | |
| } | |
| set ls_stats_events [lreplace $ls_stats_events 0 0] | |
| set channel [lindex $event 1] | |
| set name [lindex $event 2] | |
| set arguments [lindex $event 3] | |
| putmsg "#labspace.stats" "channel: $channel, name: $name, args: \"[join $arguments {" "}]\"" | |
| } | |
| } | |
| proc ls_post_event {channel name args} { | |
| global ls_event_handlers | |
| foreach event_handler $ls_event_handlers { | |
| $event_handler $channel $name $args | |
| } | |
| } | |
| # returns the name of a channel that can be used for CNOTICE/CPRIVMSG | |
| # to send messages to the specified target, returns "" if no suitable | |
| # channel was found or if use of CNOTICE/CPRIVMSG is disabled | |
| proc ls_get_cmsg_chan {target} { | |
| global ls_use_cmsg | |
| set cmsg_target "" | |
| if {$ls_use_cmsg} { | |
| foreach chan [internalchannels] { | |
| if {[onchan $target $chan] && [botisop $chan]} { | |
| set cmsg_target $chan | |
| break | |
| } | |
| } | |
| } | |
| return $cmsg_target | |
| } | |
| # sends a notice to specified target | |
| proc ls_putnotc {target text} { | |
| set cnotice_chan [ls_get_cmsg_chan $target] | |
| if {$cnotice_chan == ""} { | |
| putquick "NOTICE $target :$text" | |
| } else { | |
| putquick "CNOTICE $target $cnotice_chan :$text" | |
| } | |
| } | |
| # sends a message to the specified target | |
| proc ls_putmsg {target text} { | |
| set cprivmsg_chan [ls_get_cmsg_chan $target] | |
| if {$cprivmsg_chan == ""} { | |
| putquick "PRIVMSG $target :$text" | |
| } else { | |
| putquick "CPRIVMSG $target $cprivmsg_chan :$text" | |
| } | |
| } | |
| # formats the specified role identifier for output in a message | |
| proc ls_format_role {role} { | |
| if {$role == "scientist"} { | |
| return "Mad Scientist" | |
| } elseif {$role == "investigator"} { | |
| return "Investigator" | |
| } elseif {$role == "citizen"} { | |
| return "Citizen" | |
| } elseif {$role == "lobby"} { | |
| return "Lobby" | |
| } else { | |
| return "Unknown Role" | |
| } | |
| } | |
| # formats the specified player name for output in a message (optionally | |
| # revealing that player's role in the game) | |
| proc ls_format_player {chan player {reveal 0}} { | |
| set no_highlight_player "[string range $player 0 0]\002\002[string range $player 1 end]" | |
| if {$reveal} { | |
| return "\002$no_highlight_player\002 ([ls_format_role [ls_get_role $chan $player]])" | |
| } else { | |
| return "\002$no_highlight_player\002" | |
| } | |
| } | |
| # formats a list of player names for output in a message (optionally | |
| # revealing their roles in the game) | |
| proc ls_format_players {chan players {reveal 0}} { | |
| set hr_list {} | |
| set players [lsort $players] | |
| for {set x 0} {$x < [llength $players]} {incr x} { | |
| if {$x != 0} { | |
| if {$x == [expr {[llength $players] - 1}]} { | |
| append hr_list " and " | |
| } else { | |
| append hr_list ", " | |
| } | |
| } | |
| append hr_list [ls_format_player $chan [lindex $players $x] $reveal] | |
| } | |
| return $hr_list | |
| } | |
| # returns the current state of the game | |
| proc ls_get_gamestate {chan} { | |
| global ls_gamestate | |
| if {![info exists ls_gamestate($chan)]} { | |
| ls_set_gamestate $chan lobby | |
| } | |
| return $ls_gamestate($chan) | |
| } | |
| # sets the timeout for the current game state | |
| proc ls_get_gamestate_timeout {chan} { | |
| global ls_gamestate_timeout | |
| if {![info exists ls_gamestate_timeout($chan)]} { | |
| ls_set_gamestate $chan lobby | |
| } | |
| return $ls_gamestate_timeout($chan) | |
| } | |
| # returns 1 if the game state delay was exceeded, 0 otherwise | |
| proc ls_gamestate_delay_exceeded {chan} { | |
| global ls_gamestate_delay | |
| if {![info exists ls_gamestate_delay($chan)]} { | |
| ls_set_gamestate $chan lobby | |
| } | |
| if {$ls_gamestate_delay($chan) < [clock seconds]} { | |
| return 1 | |
| } else { | |
| return 0 | |
| } | |
| } | |
| # sets the game state | |
| proc ls_set_gamestate {chan state} { | |
| global ls_gamestate ls_gamestate_timeout | |
| set ls_gamestate($chan) $state | |
| ls_set_gamestate_timeout $chan -1 | |
| ls_set_gamestate_delay $chan 30 | |
| ls_debug $chan "changed gamestate to $state" | |
| } | |
| # sets the game state timeout (in seconds) | |
| proc ls_set_gamestate_timeout {chan timeout} { | |
| global ls_gamestate_timeout | |
| if {$timeout == -1} { | |
| set ls_gamestate_timeout($chan) -1 | |
| } else { | |
| set ls_gamestate_timeout($chan) [expr {[clock seconds] + $timeout}] | |
| } | |
| ls_debug $chan "changed gamestate timeout to $timeout" | |
| } | |
| # sets the game state delay (in seconds) | |
| proc ls_set_gamestate_delay {chan delay} { | |
| global ls_gamestate_delay | |
| set ls_gamestate_delay($chan) [expr {[clock seconds] + $delay}] | |
| ls_debug $chan "changed gamestate delay to $delay" | |
| } | |
| # returns 1 if the game state timeout was exceeded, 0 otherwise | |
| proc ls_gamestate_timeout_exceeded {chan} { | |
| set now [clock seconds] | |
| set timeout [ls_get_gamestate_timeout $chan] | |
| if {$timeout != -1 && $timeout < $now} { | |
| return 1 | |
| } else { | |
| return 0 | |
| } | |
| } | |
| # returns 1 if there's a game in progress, 0 otherwise | |
| proc ls_game_in_progress {chan} { | |
| if {[ls_get_gamestate $chan] == "lobby"} { | |
| return 0 | |
| } else { | |
| return 1 | |
| } | |
| } | |
| # returns the name of the channel the specified nick is playing on | |
| # if the nick isn't playing any games "" is returned instead | |
| proc ls_chan_for_nick {nick} { | |
| foreach chan [internalchannels] { | |
| if {[ls_get_role $chan $nick] != ""} { | |
| return $chan | |
| } | |
| } | |
| return "" | |
| } | |
| proc ls_cmd_add {chan nick} { | |
| ls_add_player $chan $nick | |
| ls_advance_state $chan 1 | |
| } | |
| proc ls_pub_cmd_labspace {nick host hand chan arg} { | |
| ls_cmd_add $chan $nick | |
| } | |
| proc ls_pub_cmd_add {nick host hand chan arg} { | |
| if {[onchan match $chan]} { | |
| ls_putnotc $nick "Sorry, 'match' is on this channel. You will need to use the alternative set of lobby commands instead (!labspace / !nolabspace)." | |
| return | |
| } | |
| ls_cmd_add $chan $nick | |
| } | |
| proc ls_cmd_remove {chan nick} { | |
| ls_remove_player $chan $nick | |
| } | |
| proc ls_pub_cmd_nolabspace {nick host hand chan arg} { | |
| ls_cmd_remove $chan $nick | |
| } | |
| proc ls_pub_cmd_remove {nick host hand chan arg} { | |
| if {[onchan match $chan]} { | |
| ls_putnotc $nick "Sorry, 'match' is on this channel. You will need to use the alternative set of lobby commands instead (!labspace / !nolabspace)." | |
| return | |
| } | |
| ls_cmd_remove $chan $nick | |
| } | |
| proc ls_pub_cmd_wait {nick host hand chan arg} { | |
| if {[ls_game_in_progress $chan]} { | |
| ls_putnotc $nick "Sorry, there's no lobby at the moment." | |
| } | |
| if {[ls_get_role $chan $nick] != "lobby"} { | |
| ls_putnotc $nick "Sorry, you need to be in the lobby to use this command." | |
| return | |
| } | |
| # extend the timeout / delay for the lobby game state | |
| ls_set_gamestate_timeout $chan 120 | |
| ls_set_gamestate_delay $chan 45 | |
| ls_putmsg $chan "Lobby timeout was reset." | |
| } | |
| proc ls_cmd_kill {nick victim} { | |
| global ls_kill_messages | |
| set chan [ls_chan_for_nick $nick] | |
| set victim [string trim $victim] | |
| if {$chan == ""} { | |
| ls_putnotc $nick "You haven't joined any game lobby." | |
| return | |
| } | |
| if {[ls_get_role $chan $nick] != "scientist"} { | |
| ls_putnotc $nick "You need to be a scientist to use this command." | |
| return | |
| } | |
| if {[ls_get_gamestate $chan] != "kill"} { | |
| ls_putnotc $nick "Sorry, you can't use this command right now." | |
| return | |
| } | |
| if {[ls_get_active $chan $nick] != 1} { | |
| ls_putnotc $nick "Sorry, it's not your turn to choose a victim." | |
| return | |
| } | |
| if {[ls_get_role $chan $victim] == ""} { | |
| ls_putnotc $nick "Sorry, [ls_format_player $chan $victim] isn't playing the game." | |
| return | |
| } | |
| if {[rand 100] > 85} { | |
| ls_putmsg $chan "The scientists' attack was not successful tonight. Nobody died." | |
| ls_post_event $chan kill_failed $nick $victim | |
| } else { | |
| ls_devoice_player $chan $victim | |
| if {[string equal -nocase $victim $nick]} { | |
| ls_putmsg $chan "[ls_format_player $chan $victim 1] committed suicide." | |
| } else { | |
| if {[ls_get_role $chan $victim] == "scientist"} { | |
| ls_putmsg $chan "[ls_format_player $chan $victim 1] was brutally murdered. Oops." | |
| } else { | |
| set index [rand [llength $ls_kill_messages]] | |
| set format [lindex $ls_kill_messages [rand [llength $ls_kill_messages]]] | |
| set message [string map [list "%NICK%" [ls_format_player $chan $victim 1]] $format] | |
| ls_putmsg $chan $message | |
| } | |
| } | |
| ls_remove_player $chan $victim 1 | |
| ls_post_event $chan kill_success $nick $victim | |
| } | |
| ls_set_gamestate $chan investigate | |
| ls_advance_state $chan | |
| } | |
| proc ls_msg_cmd_kill {nick host hand text} { | |
| ls_putnotc $nick "Note: /msg resets your idle time which might be used by other players to determine your role. You should use /notice instead." | |
| ls_cmd_kill $nick $text | |
| } | |
| proc ls_notc_cmd_kill {nick host hand text {dest ""}} { | |
| ls_cmd_kill $nick [join [lrange [split $text] 1 end]] | |
| } | |
| proc ls_cmd_investigate {nick victim} { | |
| set chan [ls_chan_for_nick $nick] | |
| set victim [string trim $victim] | |
| if {$chan == ""} { | |
| ls_putnotc $nick "You haven't joined any game lobby." | |
| return | |
| } | |
| if {[ls_get_role $chan $nick] != "investigator"} { | |
| ls_putnotc $nick "You need to be an investigator to use this command." | |
| return | |
| } | |
| if {[ls_get_gamestate $chan] != "investigate"} { | |
| ls_putnotc $nick "Sorry, you can't use this command right now." | |
| return | |
| } | |
| if {[ls_get_role $chan $victim] == ""} { | |
| ls_putnotc $nick "Sorry, [ls_format_player $chan $victim] isn't playing the game." | |
| return | |
| } | |
| set investigators [ls_get_players $chan investigator] | |
| foreach investigator $investigators { | |
| if {![string equal -nocase $nick $investigator]} { | |
| ls_putnotc $investigator "Another investigator picked a target." | |
| } | |
| } | |
| if {[rand 100] > 85} { | |
| ls_putmsg $chan "[ls_format_player $chan $nick]'s fine detective work reveals [ls_format_player $chan $victim]'s role: [ls_format_role [ls_get_role $chan $victim]]" | |
| } | |
| if {[string equal -nocase $nick $victim]} { | |
| ls_putnotc $nick "You're the investigator. Excellent detective work!" | |
| } else { | |
| ls_putnotc $nick "[ls_format_player $chan $victim]'s role is: [ls_format_role [ls_get_role $chan $victim]]" | |
| } | |
| ls_post_event $chan investigate $nick $victim [ls_get_role $chan $victim] | |
| ls_set_gamestate $chan vote | |
| ls_advance_state $chan | |
| } | |
| proc ls_msg_cmd_investigate {nick host hand text} { | |
| ls_putnotc $nick "Note: /msg resets your idle time which might be used by other players to determine your role. You should use /notice instead." | |
| ls_cmd_investigate $nick $text | |
| } | |
| proc ls_notc_cmd_investigate {nick host hand text {dest ""}} { | |
| ls_cmd_investigate $nick [join [lrange [split $text] 1 end]] | |
| } | |
| proc ls_cmd_vote {nick victim} { | |
| set chan [ls_chan_for_nick $nick] | |
| set victim [string trim $victim] | |
| if {$victim == ""} { | |
| ls_putnotc $nick "Syntax: vote <nick>" | |
| return | |
| } | |
| if {$chan == ""} { | |
| ls_putnotc $nick "You haven't joined any game lobby." | |
| return | |
| } | |
| if {[ls_get_gamestate $chan] != "vote"} { | |
| ls_putnotc $nick "Sorry, you can't use this command right now." | |
| return | |
| } | |
| if {[ls_get_role $chan $victim] == ""} { | |
| ls_putnotc $nick "Sorry, [ls_format_player $chan $victim] isn't playing the game." | |
| return | |
| } | |
| if {[string equal -nocase [ls_get_vote $chan $nick] $victim]} { | |
| ls_putnotc $nick "You already voted for [ls_format_player $chan $victim]." | |
| return | |
| } | |
| ls_set_vote $chan $nick $victim | |
| ls_putnotc $nick "Done." | |
| ls_advance_state $chan | |
| } | |
| proc ls_msg_cmd_vote {nick host hand text} { | |
| ls_putnotc $nick "Note: /msg resets your idle time which might be used by other players to determine your role. You should use /notice instead." | |
| ls_cmd_vote $nick $text | |
| } | |
| proc ls_notc_cmd_vote {nick host hand text {dest ""}} { | |
| ls_cmd_vote $nick [join [lrange [split $text] 1 end]] | |
| } | |
| proc ls_cmd_smite {nick victim} { | |
| set victim [string trim $victim] | |
| set chan [ls_chan_for_nick $victim] | |
| if {$victim == ""} { | |
| ls_putnotc $nick "Syntax: smite <nick>" | |
| return | |
| } | |
| if {$chan == ""} { | |
| ls_putnotc $nick "[ls_format_player $chan $victim] isn't in any lobby." | |
| return | |
| } | |
| putmsg $chan "[ls_format_player $chan $victim 1] was struck by lightning." | |
| ls_remove_player $chan $victim 1 | |
| } | |
| proc ls_msg_cmd_smite {nick host hand text} { | |
| ls_putnotc $nick "Note: /msg resets your idle time which might be used by other players to determine your role. You should use /notice instead." | |
| ls_cmd_smite $nick $text | |
| } | |
| proc ls_notc_cmd_smite {nick host hand text {dest ""}} { | |
| ls_cmd_smite $nick [join [lrange [split $text] 1 end]] | |
| } | |
| proc ls_timer_announce_players {arg} { | |
| set user [lindex $arg 0] | |
| set chan [lindex $arg 1] | |
| setctx $user | |
| ls_announce_players $chan | |
| flushmode $chan | |
| } | |
| proc ls_announce_players {chan} { | |
| set new_players [list] | |
| foreach player [ls_get_players $chan] { | |
| if {![ls_get_announced $chan $player]} { | |
| lappend new_players $player | |
| ls_set_announced $chan $player 1 | |
| ls_voice_player $chan $player | |
| } | |
| } | |
| if {[llength $new_players] > 0} { | |
| ls_putmsg $chan "[ls_format_players $chan $new_players] joined the game ([llength [ls_get_players $chan]] players in the lobby)." | |
| } | |
| } | |
| proc ls_add_player {chan nick {forced 0}} { | |
| set role [ls_get_role $chan $nick] | |
| if {$role != ""} { | |
| return | |
| } | |
| if {!$forced} { | |
| if {[ls_game_in_progress $chan]} { | |
| ls_putnotc $nick "Sorry, you can't join the game right now." | |
| return | |
| } | |
| if {[ls_chan_for_nick $nick] != ""} { | |
| ls_putnotc $nick "Sorry, you can't play on multiple channels at once." | |
| return | |
| } | |
| } | |
| ls_set_role $chan $nick lobby | |
| ls_set_announced $chan $nick $forced | |
| if {!$forced} { | |
| ls_set_announced $chan $nick 0 | |
| internaltimer 5 0 ls_timer_announce_players [list [getctx] $chan] | |
| ls_putnotc $nick "You were added to the lobby." | |
| } else { | |
| ls_set_announced $chan $nick 1 | |
| ls_voice_player $chan $nick | |
| } | |
| ls_set_gamestate_delay $chan 30 | |
| ls_set_gamestate_timeout $chan 90 | |
| } | |
| proc ls_voice_player {chan nick} { | |
| if {![isvoice $nick $chan]} { | |
| pushmode $chan +v $nick | |
| } | |
| } | |
| proc ls_devoice_player {chan nick} { | |
| if {[isvoice $nick $chan]} { | |
| pushmode $chan -v $nick | |
| flushmode $chan | |
| } | |
| } | |
| proc ls_remove_player {chan nick {forced 0}} { | |
| set role [ls_get_role $chan $nick] | |
| if {$role == ""} { | |
| return | |
| } | |
| ls_set_role $chan $nick "" | |
| pushmode $chan -v $nick | |
| foreach player [ls_get_players $chan] { | |
| if {[ls_get_vote $chan $player] == $nick} { | |
| ls_set_vote $chan $player "" | |
| } | |
| } | |
| if {!$forced} { | |
| if {[ls_get_announced $chan $nick]} { | |
| if {[ls_game_in_progress $chan]} { | |
| ls_putmsg $chan "[ls_format_player $chan $nick] committed suicide. Goodbye, cruel world." | |
| } else { | |
| ls_putmsg $chan "[ls_format_player $chan $nick] left the game ([llength [ls_get_players $chan]] players in the lobby)." | |
| } | |
| } | |
| ls_putnotc $nick "You were removed from the lobby." | |
| ls_set_gamestate_delay $chan 30 | |
| ls_set_gamestate_timeout $chan 90 | |
| } | |
| } | |
| proc ls_get_players {chan {role ""}} { | |
| set players [list] | |
| foreach nick [internalchanlist $chan] { | |
| if {[ls_get_role $chan $nick] == ""} { | |
| continue | |
| } | |
| if {$role == "" || [ls_get_role $chan $nick] == $role} { | |
| lappend players $nick | |
| } | |
| } | |
| return $players | |
| } | |
| proc ls_get_role {chan nick} { | |
| return [bncgettag $chan $nick ls_role] | |
| } | |
| proc ls_set_role {chan nick role} { | |
| bncsettag $chan $nick ls_role $role | |
| if {$role != "" && $role != "lobby"} { | |
| ls_putnotc $nick "Your role has been changed to '[ls_format_role $role]'." | |
| } | |
| ls_post_event $chan set_role $nick $role | |
| } | |
| proc ls_get_vote {chan nick} { | |
| return [bncgettag $chan $nick ls_vote] | |
| } | |
| proc ls_set_vote {chan nick vote} { | |
| if {[string equal -nocase [ls_get_vote $chan $nick] $vote]} { | |
| return | |
| } | |
| if {$vote != ""} { | |
| set count 0 | |
| foreach player [ls_get_players $chan] { | |
| if {[string equal -nocase [ls_get_vote $chan $player] $vote]} { | |
| incr count | |
| } | |
| } | |
| # increase count for this new vote | |
| incr count | |
| if {![string equal -nocase $nick $vote]} { | |
| if {[ls_get_vote $chan $nick] != ""} { | |
| ls_putmsg $chan "[ls_format_player $chan $nick] changed their vote to [ls_format_player $chan $vote] ($count votes)." | |
| } else { | |
| ls_putmsg $chan "[ls_format_player $chan $nick] voted for [ls_format_player $chan $vote] ($count votes)." | |
| } | |
| } else { | |
| ls_putmsg $chan "[ls_format_player $chan $nick] voted for himself. Oops! ($count votes)" | |
| } | |
| ls_post_event $chan set_vote $nick $vote | |
| } | |
| bncsettag $chan $nick ls_vote $vote | |
| } | |
| proc ls_get_active {chan nick} { | |
| return [bncgettag $chan $nick ls_active] | |
| } | |
| proc ls_set_active {chan nick active} { | |
| bncsettag $chan $nick ls_active $active | |
| } | |
| proc ls_get_announced {chan nick} { | |
| set result [bncgettag $chan $nick ls_announced] | |
| if {$result == ""} { | |
| return 0 | |
| } | |
| return $result | |
| } | |
| proc ls_set_announced {chan nick announced} { | |
| bncsettag $chan $nick ls_announced $announced | |
| } | |
| proc ls_pick_player {playersVar} { | |
| upvar $playersVar players | |
| # work-around for a crash bug in 'rand' | |
| if {[llength $players] == 0} { return } | |
| set idx [rand [llength $players]] | |
| set player [lindex $players $idx] | |
| set players [lreplace $players $idx $idx] | |
| return $player | |
| } | |
| proc ls_number_scientists {numPlayers} { | |
| return [expr {int(ceil(($numPlayers - 2) / 5.0))}] | |
| } | |
| proc ls_start_game {chan} { | |
| set players [ls_get_players $chan] | |
| pushmode $chan +m | |
| # make sure all players are voiced and everyone else is de-voiced | |
| foreach nick [internalchanlist $chan] { | |
| if {[lsearch -exact $players $nick] != -1} { | |
| ls_voice_player $chan $nick | |
| } else { | |
| ls_devoice_player $chan $nick | |
| } | |
| } | |
| flushmode $chan | |
| foreach player $players { | |
| ls_set_role $chan $player lobby | |
| } | |
| ls_putmsg $chan "Starting the game..." | |
| ls_post_event $chan start_game | |
| # pick scientists | |
| set scientists_count 0 | |
| set scientists_needed [ls_number_scientists [llength $players]] | |
| while {$scientists_count < $scientists_needed} { | |
| set scientist [ls_pick_player players] | |
| ls_set_role $chan $scientist scientist | |
| incr scientists_count | |
| } | |
| # notify scientists about each other | |
| foreach scientist [ls_get_players $chan scientist] { | |
| foreach scientist_notify [ls_get_players $chan scientist] { | |
| if {![string equal -nocase $scientist $scientist_notify]} { | |
| ls_putnotc $scientist_notify "[ls_format_player $chan $scientist] is also a scientist." | |
| } | |
| } | |
| } | |
| # pick investigator | |
| set investigator [ls_pick_player players] | |
| ls_set_role $chan $investigator investigator | |
| # rest of the players are citizens | |
| foreach player $players { | |
| ls_set_role $chan $player citizen | |
| } | |
| ls_set_gamestate $chan kill_pending | |
| utimer 2 [list ls_start_game_delayed $chan] | |
| } | |
| proc ls_start_game_delayed {chan} { | |
| if {[queuesize all] > 0} { | |
| utimer 2 [list ls_start_game_delayed $chan] | |
| return | |
| } | |
| set roles [list] | |
| foreach role [list scientist investigator citizen] { | |
| lappend roles "[llength [ls_get_players $chan $role]]x [ls_format_role $role]" | |
| } | |
| ls_putmsg $chan "Roles have been assigned: [join $roles ", "] - Good luck!" | |
| ls_set_gamestate $chan kill | |
| ls_advance_state $chan | |
| } | |
| proc ls_stop_game {chan} { | |
| ls_set_gamestate $chan lobby | |
| foreach nick [ls_get_players $chan] { | |
| ls_remove_player $chan $nick 1 | |
| } | |
| pushmode $chan -m | |
| puthelp "PRIVMSG $chan :-hl Labspace" | |
| } | |
| proc ls_pubm_handler {nick uhost hand chan text} { | |
| global ls_min_players | |
| set players [ls_get_players $chan] | |
| if {[ls_get_gamestate $chan] == "lobby" && [llength $players] < $ls_min_players} { | |
| ls_set_gamestate_delay $chan 90 | |
| ls_set_gamestate_timeout $chan 150 | |
| } | |
| } | |
| proc ls_leave_handler {nick uhost hand chan {msg ""}} { | |
| if {[ls_get_role $chan $nick] != ""} { | |
| ls_remove_player $chan $nick | |
| ls_advance_state $chan | |
| } | |
| } | |
| proc ls_kick_handler {nick uhost hand chan target reason} { | |
| if {[ls_get_role $chan $nick] != ""} { | |
| ls_remove_player $chan $nick | |
| ls_advance_state $chan | |
| } | |
| } | |
| proc ls_nick_handler {nick uhost hand chan newnick} { | |
| foreach player [ls_get_players $chan] { | |
| if {[ls_get_vote $chan $player] == $nick} { | |
| ls_set_vote $chan $player $newnick | |
| } | |
| } | |
| } | |
| proc ls_timer_advance_state {user} { | |
| setctx $user | |
| foreach chan [internalchannels] { | |
| ls_advance_state $chan 1 | |
| flushmode $chan | |
| } | |
| } | |
| proc ls_advance_state {chan {delayed 0}} { | |
| global botnick ls_min_players | |
| if {$delayed && ![ls_gamestate_delay_exceeded $chan]} { | |
| return | |
| } | |
| ls_set_gamestate_delay $chan 30 | |
| set players [ls_get_players $chan] | |
| set scientists [ls_get_players $chan scientist] | |
| set investigators [ls_get_players $chan investigator] | |
| # game start condition | |
| if {![ls_game_in_progress $chan]} { | |
| if {[llength $players] < $ls_min_players} { | |
| if {[llength $players] > 0} { | |
| if {[ls_gamestate_timeout_exceeded $chan]} { | |
| ls_putmsg $chan "Lobby was closed because there aren't enough players." | |
| ls_stop_game $chan | |
| } else { | |
| ls_putmsg $chan "Game will start when there are at least ${ls_min_players} players." | |
| } | |
| } | |
| } else { | |
| ls_start_game $chan | |
| } | |
| return | |
| } | |
| # winning condition for scientists | |
| if {[llength $scientists] >= [expr {[llength $players] - [llength $scientists]}]} { | |
| ls_putmsg $chan "There are equal to or more scientists than citizens. Science wins again: [ls_format_players $chan $scientists 1]" | |
| ls_post_event $chan scientists_win | |
| ls_stop_game $chan | |
| return | |
| } | |
| # winning condition for citizens | |
| if {[llength $scientists] == 0} { | |
| ls_putmsg $chan "All scientists have been eliminated. The citizens win this round: [ls_format_players $chan $players 1]" | |
| ls_post_event $chan citizens_win | |
| ls_stop_game $chan | |
| return | |
| } | |
| # make sure there's progress towards the game's end | |
| set state [ls_get_gamestate $chan] | |
| set timeout [ls_get_gamestate_timeout $chan] | |
| if {$state == "kill"} { | |
| if {$timeout == -1} { | |
| set candidates $scientists | |
| set active_scientist [ls_pick_player candidates] | |
| foreach scientist $scientists { | |
| if {[string equal -nocase $active_scientist $scientist]} { | |
| ls_set_active $chan $scientist 1 | |
| ls_post_event $chan set_active_scientist $scientist | |
| ls_putnotc $scientist "It's your turn to select a citizen to kill. Use /notice $botnick kill <nick> to kill someone." | |
| } else { | |
| ls_set_active $chan $scientist 0 | |
| ls_putnotc $scientist "[ls_format_player $chan $active_scientist] is choosing a victim." | |
| } | |
| } | |
| if {[llength $scientists] > 1} { | |
| ls_putmsg $chan "The citizens are asleep while the mad scientists are choosing a target." | |
| } else { | |
| ls_putmsg $chan "The citizens are asleep while the mad scientist is choosing a target." | |
| } | |
| ls_set_gamestate_timeout $chan 120 | |
| } elseif {[ls_gamestate_timeout_exceeded $chan]} { | |
| ls_putmsg $chan "The scientists failed to set their alarm clocks. Nobody dies tonight." | |
| ls_post_event $chan scientists_timeout | |
| ls_set_gamestate $chan investigate | |
| ls_advance_state $chan | |
| } else { | |
| ls_putmsg $chan "The scientists still need to pick someone to kill." | |
| } | |
| } | |
| if {$state == "investigate"} { | |
| if {[llength $investigators] == 0} { | |
| # the investigators are already dead | |
| ls_set_gamestate $chan vote | |
| ls_advance_state $chan | |
| return | |
| } | |
| if {$timeout == -1} { | |
| set candidates $investigators | |
| set active_investigator [ls_pick_player candidates] | |
| foreach investigator $investigators { | |
| if {[string equal -nocase $active_investigator $investigator]} { | |
| ls_set_active $chan $investigator 1 | |
| ls_post_event $chan set_active_investigator $investigator | |
| ls_putnotc $investigator "You need to choose someone to investigate: /notice $botnick investigate <nick>" | |
| } else { | |
| ls_set_active $chan $investigator 0 | |
| ls_putnotc $investigator "Another investigator is choosing a target." | |
| } | |
| } | |
| if {[llength $investigators] > 1} { | |
| ls_putmsg $chan "It's now up to the investigators to find the mad scientists." | |
| } else { | |
| ls_putmsg $chan "It's now up to the investigator to find the mad scientists." | |
| } | |
| ls_set_gamestate_timeout $chan 120 | |
| } elseif {[ls_gamestate_timeout_exceeded $chan]} { | |
| ls_putmsg $chan "Looks like the investigator is still firmly asleep." | |
| ls_post_event $chan investigator_timeout | |
| ls_set_gamestate $chan vote | |
| ls_advance_state $chan | |
| } else { | |
| ls_putmsg $chan "The investigator still needs to do their job." | |
| } | |
| } | |
| if {$state == "vote"} { | |
| set missing_votes [list] | |
| foreach player $players { | |
| if {[ls_get_vote $chan $player] == ""} { | |
| lappend missing_votes $player | |
| } | |
| } | |
| if {$timeout == -1} { | |
| foreach player $players { | |
| ls_set_vote $chan $player "" | |
| } | |
| ls_putmsg $chan "It's now up to the citizens to vote who to lynch (via /notice $botnick vote <nick>)." | |
| ls_set_gamestate_timeout $chan 120 | |
| } elseif {[ls_gamestate_timeout_exceeded $chan] || [llength $missing_votes] == 0} { | |
| if {[ls_gamestate_timeout_exceeded $chan]} { | |
| } | |
| array unset votes | |
| foreach player $players { | |
| set vote [ls_get_vote $chan $player] | |
| if {![info exists votes($player)]} { | |
| set votes($player) 0 | |
| } | |
| set real_vote "" | |
| foreach nick [internalchanlist $chan] { | |
| if {[string equal -nocase $nick $vote]} { | |
| set real_vote $nick | |
| break | |
| } | |
| } | |
| if {$real_vote == ""} { | |
| continue | |
| } | |
| set vote $real_vote | |
| if {![info exists votes($vote)]} { | |
| set votes($vote) 0 | |
| } | |
| incr votes($vote) | |
| } | |
| array unset votest_reversed | |
| foreach {vote count} [array get votes] { | |
| if {![info exists votes_reversed($count)]} { | |
| set votes_reversed($count) [list] | |
| } | |
| lappend votes_reversed($count) $vote | |
| } | |
| set vote_counts [lsort -integer -decreasing [array names votes_reversed]] | |
| set vote_msg_parts [list] | |
| foreach vote_count $vote_counts { | |
| if {$vote_count == 0} { | |
| continue | |
| } | |
| lappend vote_msg_parts "${vote_count}x [ls_format_players $chan $votes_reversed($vote_count)]" | |
| } | |
| ls_putmsg $chan "Votes: [join $vote_msg_parts ", "]" | |
| set candidates $votes_reversed([lindex $vote_counts 0]) | |
| set victim [lindex $candidates [rand [llength $candidates]]] | |
| ls_debug $chan "lynch candidates: [join $candidates ", "] - picked: $victim" | |
| ls_devoice_player $chan $victim | |
| ls_putmsg $chan "[ls_format_player $chan $victim 1] was lynched by the angry mob." | |
| ls_post_event $chan player_lynched $victim [ls_get_role $chan $victim] | |
| ls_remove_player $chan $victim 1 | |
| ls_set_gamestate $chan kill | |
| ls_advance_state $chan | |
| } elseif {$delayed} { | |
| ls_putmsg $chan "Some of the citizens still need to vote: [ls_format_players $chan $missing_votes]" | |
| } | |
| } | |
| } |