## This is the error message:

[03:26] can't read "quizconf(questionset)": no such element in array
    while executing
"mx_read_questions $quizconf(questionset)"
    (file "scripts/icquiz.tcl" line 2697)
    invoked from within
"source scripts/icquiz.tcl"


## THIS THE SCRIPT ##
# system stuff
variable quizbasedir        quiz
variable datadir            $quizbasedir/quizdata
variable configfile         $quizbasedir/icquiz.rc

variable rankfile           $datadir/rank.data
variable allstarsfile       $datadir/rankallstars.data
variable userqfile          $datadir/questions.user.en
variable commentsfile       $datadir/comments.txt
variable channeltipfile     $datadir/channeltips.txt
variable channelrulesfile   $datadir/channelrules.txt
variable pricesfile         $datadir/prices.txt

#
# Configuration map
#
variable quizconf

set quizconf(quizchannel)        "#coool"
set quizconf(quizloglevel)       1
 
# several global numbers
set quizconf(maxranklines)       5
set quizconf(tipcycle)           5
set quizconf(useractivetime)     240
set quizconf(userqbufferlength)  5
#set quizconf(winscore)          5530

# timer delays in seconds
set quizconf(askdelay)           15
set quizconf(tipdelay)           30

# safety features and other configs
set quizconf(lastwinner_restriction)  no
set quizconf(lastwinner_max_games)    3
set quizconf(colorize)                yes
set quizconf(monthly_allstars)        yes
set quizconf(channeltips)             no
set quizconf(pausemoderated)          no
set quizconf(userquestions)           no
set quizconf(msgwhisper)              no
set quizconf(channelrules)            no
set quizconf(prices)                  no
set quizconf(stripumlauts)            no


##
###########################################################################

##
## stuff for the game state
##
# values = stopped, paused, asked, waittoask, halted
variable quizstate "halted"
variable statepaused ""
variable statemoderated ""
variable usergame 0
variable timeasked [unixtime]
variable revoltmax 0
# values = newgame, stop, halt, exit
variable aftergame "newgame"
variable channeltips ""
variable channelrules ""
variable prices ""

#
# variables for the ranks and user handling
#
variable timerankreset [unixtime]
variable userlist
variable allstarsarray
variable revoltlist ""
variable lastsolver ""
variable lastsolvercount 0
variable lastwinner ""
variable lastwinnercount 0
variable allstars_starttime 0

#
# stuff for the question
#
variable tiplist ""
variable theq
variable qnumber 0
variable qnum_thisgame 0
variable userqnumber 0
variable tipno 0
variable qlist ""
variable qlistorder ""
variable userqlist ""

#
# doesn't fit elsewhere
#
variable whisperprefix "NOTICE"

##################################################
## bindings

# userquest and other user (public) commands
bind pubm - * tmcquiz_pubm
bind pub - !play tmcquiz_ask
bind pub - !stop tmcquiz_stops
bind pub n !exit tmcquiz_exit
bind msg f !userquest tmcquiz_userquest
bind msg - !usercancel tmcquiz_usercancel
bind msg - !usersolve tmcquiz_usersolve
bind pub - !help tmcquiz_pub_help
bind pub - !score tmcquiz_pub_score
bind pub - !Top10 tmcquiz_pub_rank

# Some events the bot reacts on
#bind nick - * tmcquiz_on_nickchanged
#bind join - * tmcquiz_on_joined
# bind mode - "*m" tmcquiz_on_moderated
bind evnt - prerehash mx_event
bind evnt - rehash mx_event

## DEBUG
bind dcc n !colors tmcquiz_colors

#selamat
#  ucapan selamat
set tgcongrats [list "Congratulations" "Nicely done!" "Nice going" "There you go" "You got it" "Wow!!" "Nice job" "Did your mom tell you that?"]

#  ucapan kalo gak ada yg bisa jawab
set tgnobodygotit [list "Next time will be easier..." "Where is everybody?" "Come on guys!!" "Is this too hard for you?" "Wake up!!" "Maybe next time"]

#  ucapan pertanyaan dak ada yg bisa jawab trus pertanyaan yg berikutnya
set tgtrythenextone [list "Try the next one..." "Get ready for the next one..." "Maybe you'll get the next one..." "Try and get the next one..." "Here comes the next one..."]

###########################################################################
#
# Helptext
#
###########################################################################
set quizshorthelp [list \
	"efnet-jakarta@gmail.com" \
	"!play - !stop - !score - !top10" ]


###########################################################################
#
# bot running commands
#
###########################################################################

## stop
## stop everything and kill all timers
proc tmcquiz_stop {handle idx arg} {
    global quizstate
    global quizconf
    variable t
    variable prefix 


    ## called directly?
    if {[info level] != 1} {
	set prefix
    } else {
	set prefix
    }

    set quizstate "stopped"

    ## kill timers
    foreach t [utimers] {
	if {[lindex $t 1] == "mx_timer_ask" || [lindex $t 1] == "mx_timer_tip"} {
	    killutimer [lindex $t 2]
	}
    }

    mx_log "--- Game stopped."
    mxirc_say $quizconf(quizchannel) "The Game has stopped!."
    return 1
}


## halt
## halt everything and kill all timers
proc tmcquiz_halt {handle idx arg} {
    global quizstate banner bannerspace
    global quizconf

    variable t
    variable prefix

    ## called directly?
    if {[info level] != 1} {
	set prefix [bannerspace]
    } else {
	set prefix
    }

    set quizstate "halted"
    
    ## kill timers
    foreach t [utimers] {
	if {[lindex $t 1] == "mx_timer_ask" || [lindex $t 1] == "mx_timer_tip"} {
	    killutimer [lindex $t 2]
	}
    }

    mx_log "--- Game halted."
    mxirc_say $quizconf(quizchannel) "0,3Type 9!play0,3to start."
    return 1
}


## reload questions
proc tmcquiz_reload {handle idx arg} {
    global qlist quizconf
    global datadir

    variable alist ""
    variable banks
    variable suffix

    set arg [string trim $arg]
    if {$arg == ""} {
	# get question files
	set alist [glob -nocomplain "$datadir/questions.*"]

	# get suffixes
	foreach file $alist {
	    regexp "^.*\\.(\[^\\.\]+)$" $file foo suffix
	    set banks($suffix) 1
	}

	# report them
	mxirc_dcc $idx "There are the following question banks available (current: $quizconf(questionset)): [lsort [array names banks]]"
    } else {
	if {[mx_read_questions $arg] != 0} {
	    mxirc_dcc $idx "There was an error reading files for $arg."
	    mxirc_dcc $idx "There are [llength $qlist] questions available."
	} else {
	    mxirc_dcc $idx "Reloaded database, [llength $qlist] questions."
	    set quizconf(questionset) $arg
	}
    }

    return 1
}



## exit -- finish da thing and logoff
proc tmcquiz_exit {nick host handle channel idx} {
    global rankfile uptime botnick
    global quizconf
    mx_log "--- EXIT requested."
    mxirc_say $quizconf(quizchannel) "0,1Be Right Back."
    tmcquiz_rank_save $handle $idx {}
    tmcquiz_saveuserquests $handle $idx "all"
    tmcquiz_config_save $handle $idx {}
    mxirc_dcc $idx "$botnick now exits."
    mx_log "--- $botnick exited"
    mx_log "**********************************************************************"

    utimer 5 die
}

###########################################################################
#
# commands for the questions
#
###########################################################################

## something was said. Solution?
proc tmcquiz_pubm {nick host handle channel text} {
    global quizstate 
    global timeasked theq aftergame
    global usergame revoltlist
    global lastsolver lastsolvercount
    global lastwinner lastwinnercount
    global botnick
    global userlist channeltips prices
    global quizconf
    global tgcongrats 

    variable bestscore 0 lastbestscore 0 lastbest ""
    variable userarray
    variable authorsolved 0 waitforrank 0 gameend 0

#    ## only accept chatter on quizchannel
#    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
#	return
#    }

    ## record that the $nick spoke and create entries for unknown people
    mx_getcreate_userentry $nick $host
    array set userarray $userlist($nick)
    set hostmask $userarray(mask)

    ## not in asking state?
    if {$quizstate != "asked"} {
	return
    }

    # nick has revolted
    if {[lsearch -exact $revoltlist $hostmask] != -1} {
	return
    }

    # nick is author of userquest
    if {([info exists theq(Author)] && [mx_str_ieq $nick $theq(Author)])
    || ([info exists theq(Hostmask)] && [mx_str_ieq [maskhost $host] $theq(Hostmask)])} {
	set authorsolved 1
    }

    ## tweak german umlauts in input
    set text [mx_tweak_umlauts $text]

    if {[regexp -nocase -- $theq(Regexp) $text]} {
	## reset quiz state related stuff (and save userquestions)
	mx_answered
	set duration [mx_duration $timeasked]

	# if it wasn't the author
	if {!$authorsolved} {
	    ## save last top score for the test if reset is near (later below)
	    set lastbest [lindex [lsort -command mx_sortrank [array names userlist]] 0]
	    if {$lastbest == ""} {
		set lastbestscore 0
	    } else {
		array set aa $userlist($lastbest)
		set lastbestscore $aa(score)
	    }

	    ## record nick for bonus points
	    if {[mx_str_ieq [maskhost $host] $lastsolver]} {
		incr lastsolvercount
	    } else {
		set lastsolver [maskhost $host]
		set lastsolvercount 1
	    }

#	    ## ignore games_max in a row winner
#	    if {[mx_str_ieq [maskhost $host] $lastwinner]
#	    && $lastwinnercount >= $quizconf(lastwinner_max_games)
#	    && $quizconf(lastwinner_restriction) == "yes"} {
#		return
#	    }

	    ## save score (set started time to time of first point)
	    incr userarray(score) $theq(Score)
	    if {$userarray(score) == 1} {
		set userarray(started) [unixtime]
	    }
	    set userlist($nick) [array get userarray]

	    ## tell channel, that the question is solved
	    mx_log "--- question answered in $duration by $nick with \"$text\", now $userarray(score) points"

	    regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
	    mxirc_say $channel "4,2[lindex $tgcongrats [rand [llength $tgcongrats]]]8,2 11,2$nick 4,2 R0,2ight 4,2a0,2nswered 9,2$answer 4,2i0,2n 15,2 $duration 4,2S0,2core 8+$theq(Score) pts 4,2T0,2otal 4,2N0,2ilai 4$userarray(score)0,2 points 11,2Rangking13,2 [mx_get_rank_pos $nick]"



	    ## honor good games!
	    if {$lastsolvercount == 3} {
		mxirc_say $channel "0,1Cool! 3 in a row! Bonus 9+5 1. 0,14$nick0 how did you do that?"
                tmcquiz_rank_set $botnick 0 "$nick +10"
	    } elseif {$lastsolvercount == 6} {
		mxirc_say $channel "0,1Did your mom tell you that? 5 answered! Bonus 9+10 1. 0,19$nick0 Nice job.."
		tmcquiz_rank_set $botnick 0 "$nick +20"
	    } elseif {$lastsolvercount == 10} {
		mxirc_say $channel "0,1Cool 10/10!! 10 answered! Bonus 9+15 1. 0,14$nick0 .... you are something..."
		mx_log "--- $nick has ten in a row.  score += 22"
		tmcquiz_rank_set $botnick 0 "$nick +30"
	    } elseif {$lastsolvercount == 15} {
		mxirc_say $channel "0,1Oh my God!! 15 answered! Bonus 9+20 1. 0,19$nick0 ... aren't you tired?"
		tmcquiz_rank_set $botnick 0 "$nick +50"
	    } elseif {$lastsolvercount == 20} {
		mxirc_say $channel "0,1Perfect! 20 answered! Bonus 9+25 1. 0,14$nick you are a legend "
		tmcquiz_rank_set $botnick 0 "$nick +100"

	    }

	    ## rankreset, if above winscore
	    # notify if this comes near
	    set best [lindex [lsort -command mx_sortrank [array names userlist]] 0]
	    if {$best == ""} {
		set bestscore 0
	    } else {
		array set aa $userlist($best)
		set bestscore $aa(score)
	    }

	    set waitforrank 0
	    if {[mx_str_ieq $best $nick] && $bestscore > $lastbestscore} {
		array set aa $userlist($best)
		# tell the end is near
		if {$bestscore >= $quizconf(winscore)} {
                    set price "."

                    if {$quizconf(prices) == "yes"} {
                        set price " [lindex $prices [rand [llength $prices]]]"
                    }

#		    mxirc_say $channel "$nick reaches $quizconf(winscore) points and wins$price"
		    set now [unixtime]
		    if {[mx_str_ieq [maskhost $host] $lastwinner]} {
			incr lastwinnercount
			if {$lastwinnercount >= $quizconf(lastwinner_max_games)
			&& $quizconf(lastwinner_restriction) == "yes"} {
#			    mxirc_notc $nick "Since you won $quizconf(lastwinner_max_games) games in a row, you will be ignored for the next game.  This is a safety feature to stop answer bots."
			}
		    } else {
			set lastwinner [maskhost $host]
			set lastwinnercount 1
		    }
		    # save $nick in allstars table
		    mx_saveallstar $now [expr $now - $aa(started)] $bestscore $nick [maskhost $host]
#		    tmcquiz_rank $botnick 0 {}
#		    tmcquiz_rank_reset $botnick {} {}
#		    set gameend 1
#		    set waitforrank 15
		} elseif {$bestscore == [expr $quizconf(winscore) / 2]} {
		    mxirc_say $channel "Halftime.  Game is won at $quizconf(winscore) points."
		} elseif {$bestscore == [expr $quizconf(winscore) - 10]} {
		    mxirc_say $channel "$best has 10 points to go."
		} elseif {$bestscore == [expr $quizconf(winscore) - 5]} {
		    mxirc_say $channel "$best has 5 points to go."
		} elseif {$bestscore >= [expr $quizconf(winscore) - 3]} {
		    mxirc_say $channel "$best has [expr $quizconf(winscore) - $bestscore] point(s) to go."
		}

		# show rank at 1/3, 2/3 of and 5 before winscore
		set spitrank 1
		foreach third [list [expr $quizconf(winscore) / 3] [expr 2 * $quizconf(winscore) / 3] [expr $quizconf(winscore) - 5]] {
		    if {$lastbestscore < $third && $bestscore >= $third && $spitrank} {
			tmcquiz_rank $botnick 0 {}
			set spitrank 0
#			set waitforrank 15
		    }
		}
		
	    }
	} else {
	    ## tell channel, that the question is solved by author
	    mx_log "--- solved after $duration by $nick with \"$text\" by author"
	    regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
	    mxirc_say $channel "0,1 $nick answered his own question after9 $duration0 and no score, still 9 $userarray(score) 1 points in rank 9 [mx_get_rank_pos $nick] 0. answered :4 $answer"

	    # remove area of tip generation tags
	    regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
#	    mxirc_say $channel "Answered: $answer"
	}


	## check if game has ended and react
	if {!$gameend || $aftergame == "newgame"} {
	    # set up ask timer
	    utimer [expr $waitforrank + $quizconf(askdelay)] mx_timer_ask
	} else {
	    mx_aftergameaction
	}
    }
}


## ask a question, start game
proc tmcquiz_ask {nick host handle channel arg} {
    global qlist quizstate botnick
    global tipno tiplist
    global userqnumber usergame userqlist
    global timeasked qnumber qlistorder theq
    global qnum_thisgame
    global userlist timerankreset
    global quizconf
    variable anum 0
    variable txt

    ## only accept chatter on quizchannel
    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
	return
    }

#   switch -exact $quizstate {
#	"paused" {
#	    mxirc_notc $nick "Game is paused."
#	    return 1
#	}
#	"stopped" {
#	    mxirc_notc $nick "Mumun Game is Stopped."
#	    return 1
#	}
#    }

    ## record that $nick spoke (prevents desert detection from stopping,
    ## when an user joins and starts the game with !ask)
    if {![mx_str_ieq $nick $botnick]} {
	mx_getcreate_userentry $nick $host
    }

    ## any questions available?
    if {[llength $qlist] == 0 && [mx_userquests_available] == 0} {
	mxirc_say $channel "Sorry, my database is empty."
    } elseif {$quizstate == "asked"} {
	## game runs, tell user the question via msg
	set txt "Current"
	if {[info exists theq(Level)]} {
	    set txt "$txt level $theq(Level)"
	}
	if {$usergame == 1} {
	    set txt "$txt user"
	}
	set txt "$txt question no. $qnum_thisgame is \""
	if {[info exists theq(Category)]} { 
	    set txt "${txt}\($theq(Category)\) "
	}
	set txt "$txt$theq(Question)\" open for [mx_duration $timeasked]"
	if {$theq(Score) > 1} {
	    set txt "$txt, worth $theq(Score) points."
	} else {	
	    set txt "$txt."
	}
	mxirc_notc $nick $txt
    } elseif {$quizstate == "waittoask" && ![mx_str_ieq $nick $botnick]} {
	## no, user has to be patient
	mxirc_notc $nick "0,1Siap?! Soal berikutnya dalam4 $quizconf(askdelay) 0detik."
    } else {
	##
	## ok, now lets see, which question to ask (normal or user)
	##

	## clear old question
	foreach k [array names theq] {
	    unset theq($k)
	}

	if {[mx_userquests_available]} {
	    ## select a user question
	    array set theq [lindex $userqlist $userqnumber]
	    set usergame 1
	    incr userqnumber
	    mx_log "--- asking a user question: $theq(Question)"
	} else {
	    variable ok 0
	    while {!$ok} {
		## select a normal question
		if {$qnumber >= [llength $qlistorder]} {
		    set qnumber 0
		    set qlistorder [mx_mixedlist [llength $qlist]]
		}
		array set theq [lindex $qlist [lindex $qlistorder $qnumber]]
		set usergame 0

		# skip question if author is about to win
		if {[info exists theq(Author)] && [info exists userlist($theq(Author))]} {
		    array set auser $userlist($theq(Author))
		    if {$auser(score) >= [expr $quizconf(winscore) - 5]} {
			mx_log "--- skipping question number $qnumber ([lindex $qlistorder $qnumber]), author is about to win"
			## clear old question
			foreach k [array names theq] {
			    unset theq($k)
			}
		    } else {
			mx_log "--- asking question number $qnumber ([lindex $qlistorder $qnumber]): $theq(Question)"
			set ok 1
		    }
		} else {
		    mx_log "--- asking question number $qnumber ([lindex $qlistorder $qnumber]): $theq(Question)"
		    set ok 1
		}
		incr qnumber
	    }
	}
	incr qnum_thisgame
	if {$qnum_thisgame == 1} {
	    set timerankreset [unixtime]
	    mx_log "---- it's the no. $qnum_thisgame in this game, rank timer started."
	} else {
	    mx_log "---- it's the no. $qnum_thisgame in this game."
	}

	##
	## ok, set some minimal required fields like score, regexp and the tiplist.
	##

	## set regexp to match
	if {![info exists theq(Regexp)]} {
	    ## mask all regexp special chars except "."
	    set aexp [mx_tweak_umlauts $theq(Answer)]
	    regsub -all "(\\+|\\?|\\*|\\^|\\$|\\(|\\)|\\\[|\\\]|\\||\\\\)" $aexp "\\\\\\1" aexp
	    # get #...# area tags for tipgeneration as regexp
	    regsub -all ".*\#(\[^\#\]*\)\#.*" $aexp "\\1" aexp
	    set theq(Regexp) $aexp 
	} else {
	    set theq(Regexp) [mx_tweak_umlauts $theq(Regexp)]
	}

	# protect embedded numbers
	if {[regexp "\[0-9\]+" $theq(Regexp)]} {
	    set newexp ""
	    set oldexp $theq(Regexp)
	    set theq(Oldexp) $oldexp

	    while {[regexp -indices "(\[0-9\]+)" $oldexp pair]} {
		set subexp [string range $oldexp [lindex $pair 0]  [lindex $pair 1]]
		set newexp "${newexp}[string range $oldexp -1 [expr [lindex $pair 0] - 1]]"
		if {[regexp -- $theq(Regexp) $subexp]} {
		    set newexp "${newexp}(^|\[^0-9\])${subexp}(\$|\[^0-9\])"
		} else {
		    set newexp "${newexp}${subexp}"
		}
		set oldexp "[string range $oldexp [expr [lindex $pair 1] + 1] [string length $oldexp]]"
	    }
	    set newexp "${newexp}${oldexp}"
	    set theq(Regexp) $newexp
	    mx_log "---- replaced regexp '$theq(Oldexp)' with '$newexp' to protect numbers."
	}

	## set score
	if {![info exists theq(Score)]} {
	    set theq(Score) 3
	}

	## set category
	## set anum [lsearch -exact $alist "Category"]
	## if {![info exists theq(Category)} {
	##    set theq(Category) "unknown"
	##}
	
	## initialize tiplist
	set anum 0
	set tiplist ""
	while {[info exists theq(Tip$anum)]} {
	    lappend tiplist $theq(Tip$anum)
	    incr anum
	}
	# No tips found?  construct standard list
	if {$anum == 0} {
	    set add "·"

	    # extract area of tip generation tags
	    if {![regsub -all ".*\#(\[^\#\]*\)\#.*" $theq(Answer) "\\1" answer]} {
		set answer $theq(Answer)
	    }

	    ## use tipcycle from questions or
	    ## generate less tips if all words shorter than $tipcycle
	    if {[info exists theq(Tipcycle)]} {
		set limit $theq(Tipcycle)
	    } else {
		set limit $quizconf(tipcycle)
		## check if at least one word long enough
		set tmplist [lsort -command mx_cmp_length -decreasing [split $answer " "]]
		# not a big word
		if {[string length [lindex $tmplist 0]] < $quizconf(tipcycle)} {
		    set limit [string length [lindex $tmplist 0]]
		}
	    }
	    
	    for {set anum 0} {$anum < $limit} {incr anum} {
		set tiptext ""
		set letterno 0
		for {set i 0} {$i < [string length $answer]} {incr i} {
		    if {([expr [expr $letterno - $anum] \% $quizconf(tipcycle)] == 0) || 
		    ([regexp "\[- \.,`'\"\]" [string range $answer $i $i] foo])} {
			set tiptext "$tiptext[string range $answer $i $i]"
			if {[regexp "\[- \.,`'\"\]" [string range $answer $i $i] foo]} {
			    set letterno -1
			}
		    } else {
			set tiptext "$tiptext$add"
		    }
		    incr letterno
		}
		lappend tiplist $tiptext
	    }
	}

	##
	## Now construct the text to print
	##
#        set ans "word : $answer"
	set txt "14,15<0>14<0>14<0>14<0>4,1 S15,1cramble Game at #indoGame4,1@15,1ALLnet -r@"
	if {[info exists theq(Level)]} {
	    set txt "$txt level $theq(Level)"
	}
	if {$usergame == 1} {
	    set txt "$txt user"
	}
	set txt "$txt 4,1S15,1oal 4,1N15,1o $qnum_thisgame"
	if {$theq(Score) > 1} {
#	    set txt "$txt"
	}
	if {[info exists theq(Author)]} {
	    set txt "$txt 4,1B15,1y9,1 $theq(Author) 14,15 <0>14<0>14<0>14<0>"
	} else {
	    set txt "Petunjuk: $txt:"
	}
#        set ans "word :[randomanswer $answer]"
#        mxirc_say $channel $ans
	mxirc_say $channel $txt


	set txt ""
	if {[info exists theq(Category)]} {
	    set txt "$txt\($theq(Category)\) $theq(Question)"
	} else {
	    set txt "14,15<0>14<0>14<0>14<0>4,1P15,1ertanyaan9,1 :0,14 $txt$theq(Question) 14,15 <0>14<0>14<0>14<0>"
	}
      set ans "14,15<0>14<0>14<0>14<0>4,1A15,1cak Kata9,1 :0,14 [randomanswer $answer] 14,15 <0>14<0>14<0>14<0>"
      set tipz "[get_InfoItem]"

 mxirc_say $channel $txt
 mxirc_say $channel $ans
 mxirc_say $channel $tipz


	set quizstate "asked"
	set tipno 0
	set timeasked [unixtime]
	## set up tip timer
	utimer $quizconf(tipdelay) mx_timer_tip
    }
}


## A user dislikes the question
proc tmcquiz_user_revolt {nick host handle channel text} {
    global revoltlist revoltmax tipno botnick quizstate
    global userlist
    global quizconf

    ## only accept revolts on the quizchannel
    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
	return
    }

    if {$quizstate == "asked"} {
	if {$tipno < 1} {
#	    mxirc_action $channel "does not react on revolts before at least one tip was given."
	    return
	}

	## ensure that the revolting user has an entry
	if {![info exists userlist($nick)]} {
	    mx_getcreate_userentry $nick $host
	}

	## calculate people needed to make a revolution (50\% of active users)
	mx_log "--- a game runs, !revolt.  revoltmax = $revoltmax"
	if {$revoltmax == 0} {
	    set now [unixtime]
	    foreach u [array names userlist] {
		array set afoo $userlist($u)
		if {[expr $now - $afoo(lastspoken)] <= $quizconf(useractivetime)} {
		    incr revoltmax
		}
	    }
	    mx_log "---- active people are $revoltmax"
	    # one and two player shoud revolt "both"
	    if {$revoltmax > 2} {
		set revoltmax [expr int(ceil(double($revoltmax) / 2))]
	    }
	    mx_log "---- people needed for a successful revolution: $revoltmax"
	}

	# records known users dislike
	if {[info exists userlist($nick)]} {
	    array set anarray $userlist($nick)
	    set hostmask $anarray(mask)
	    if {[lsearch -exact $revoltlist $hostmask] == -1} {
		mxirc_quick_notc $nick "Since you are revolting, you will be ignored for this question."
#		mxirc_action $channel "0,1sees that $nick and [llength $revoltlist] other dislike the question, you need $revoltmax people."
		lappend revoltlist $hostmask
		set anarray(lastspoken) [unixtime]
		set userlist($nick) [array get anarray]
		mx_log "--- $nick is revolting, revoltmax is $revoltmax"
	    }
	}
	if {[llength $revoltlist] >= $revoltmax} {
	    set revoltmax 0
	    mx_log "--- solution forced by revolting."
	    mxirc_action $channel "0,1will solve the question immediately."
	    tmcquiz_solve $botnick 0 {}
	}
    }
}


## solve question

proc tmcquiz_solve {handle idx arg} {
    global tgnobodygotit
    global tgtrythenextone
    global quizstate theq
    global botnick lastsolvercount lastsolver timeasked
    global quizconf

    variable txt
    variable answer
    if {$quizstate != "asked"} {
	mxirc_dcc $idx "There is no open question."
    } else {
	mx_answered
	set lastsolver ""
	set lastsolvercount 0

	if {[mx_str_ieq $botnick $handle]} {
	    set txt "4,1T15,1ime's 4,1U15,1P"
	    set solver ""
	} else {
	    set txt "0,1Manually"
	    set solver " by $handle"
	}
        regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
	set txt "4,2[lindex $tgnobodygotit [rand [llength $tgnobodygotit]]]4,2 T15,2ime's 13,2[mx_duration $timeasked]$solver4,2 U15,2p, 4,2A15,2nswer4,2: 11$answer9,2 [lindex $tgtrythenextone [rand [llength $tgtrythenextone]]]"
	mxirc_say $quizconf(quizchannel) $txt

	# remove area of tip generation tags
#	regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
#	mxirc_say $quizconf(quizchannel) "0,1The answer is: $answer"

	# remove protection of numbers from regexp
	if {[info exists theq(Oldexp)]} {
	    set theexp $theq(Oldexp)
	} else {
	    set theexp $theq(Regexp)
	}

	if {$answer != $theexp} {
#	    mxirc_say $quizconf(quizchannel) "And should match: $theexp"
	}

	mx_log "--- solved by $handle manually."
	# schedule ask
	utimer $quizconf(askdelay) mx_timer_ask
    } 
    return 1
}


## show a tip
proc tmcquiz_tip {handle idx arg} {
    global tipno quizstate
    global botnick tiplist
    global quizconf

    if {$quizstate == "asked"} {
	if {$arg != ""} {
	    mxirc_dcc $idx "Extra tip \'$arg\' will be given."
	    set tiplist [linsert $tiplist $tipno $arg]
	}
	if {$tipno == [llength $tiplist]} {
	    # enough tips, solve!
	    set tipno 0
	    tmcquiz_solve $botnick 0 {}
	} else {
	    set tiptext [lindex $tiplist $tipno]
#	    mxirc_say $quizconf(quizchannel) "Hint [expr $tipno + 1]:"
#	    mxirc_say $quizconf(quizchannel) "$tiptext"
	    foreach j [utimers] {
		if {[lindex $j 1] == "mx_timer_tip"} {
		    killutimer [lindex $j 2]
		}
	    }
	    mx_log "----- Tip number $tipno: $tiptext"
	    # only short delay after last tip
	    incr tipno
	    if {$tipno == [llength $tiplist]} {
		utimer 15 mx_timer_tip
	    } else {
		utimer $quizconf(tipdelay) mx_timer_tip
	    }
	}
    } else {
	mxirc_dcc $idx "Sorry, no question is open."
    }
    return 1
}


## schedule a userquest
proc tmcquiz_userquest {nick host handle arg} {
    global userqlist
    global quizconf
    variable uanswer "" uquestion "" umatch ""
    variable tmptext ""

    if {$quizconf(userquestions) == "no"} {
	mxirc_notc $nick "Sorry, userquestions are disabled."
	return
    }

    if {![onchan $nick $quizconf(quizchannel)]} {
	mxirc_notc $nick "Sorry, you MUST be in the quizchannel to ask questions."
    } else {
	if {[mx_userquests_available] >= $quizconf(userqbufferlength)} {
	    mxirc_notc $nick "Sorry, there are already $quizconf(userqbufferlength) user questions scheduled.  Try again later."
	} else {
	    set arg [mx_strip_colors $arg]
            if {$quizconf(stripumlauts) == "yes"} {
                set arg [mx_tweak_umlauts $arg]
            }
	    if {[regexp "^(.+)::(.+)::(.+)$" $arg foo uquestion uanswer umatch] || \
		    [regexp "(.+)::(.+)" $arg foo uquestion uanswer]} {
		set uquestion [string trim $uquestion]
		set uanswer [string trim $uanswer]
		set alist [concat "Question" "{$uquestion}" "Answer" "{$uanswer}" "Author" "{$nick}" "Hostmask" "[maskhost $host]" "Date" "[ctime [unixtime]]"]
		if {$umatch != ""} {
		    set umatch [string trim $umatch]
		    lappend alist "Regexp" "$umatch"
		    set mtext ", match \"$umatch\""
		}
		lappend userqlist $alist
		
		mxirc_notc $nick "Your quest \"$uquestion\" is scheduled with answer \"$uanswer\"$tmptext and will be asked after [expr [mx_userquests_available] - 1] questions."
		mx_log "--- Userquest scheduled by $nick: \"$uquestion\"."
	    } else {
		mxirc_notc $nick "Wrong number of parameters.  Use alike <question>::<answer>::<regexp>.  The regexp is optional and used with care." 
		mxirc_notc $nick "You said: \"$arg\".  I recognize this as: \"$uquestion\" and \"$uanswer\", regexp: \"$umatch\"."
		mx_log "--- userquest from $nick failed with: \"$arg\""
	    }
	}
    }
    return
}


## usertip
#proc tmcquiz_usertip {nick host handle arg} {
#    global quizstate usergame theq
#    global quizconf
#    
#    if {[onchan $nick $quizconf(quizchannel)]} {
#	mx_log "--- Usertip requested by $nick: \"$arg\"."
#	if {$quizstate == "asked" && $usergame == 1} {
#	    if {[info exists theq(Author)] && ![mx_str_ieq $nick $theq(Author)]} {
#		mxirc_notc $nick "No, only $theq(Author) can give tips here!"
#	    } else {
#		tmcquiz_tip $nick 0 $arg
#	    }
#	} else {
#	    mxirc_notc $nick "No usergame running."
#	}
#    } else {
#	mxirc_notc $nick "Sorry, you MUST be in the quizchannel to give tips."
#    }
#    return 1
#}


## usersolve
proc tmcquiz_usersolve {nick host handle arg} {
    global quizstate usergame theq

    mx_log "--- Usersolve requested by $nick."
    if {$quizstate == "asked" && $usergame == 1} {
	if {[info exists theq(Author)] && ![mx_str_ieq $nick $theq(Author)]} {
	    mxirc_notc $nick "No, only $theq(Author) can solve this question!"
	} else {
	    tmcquiz_solve $nick 0 {}
	}
    } else {
	mxirc_notc $nick "No usergame running."
    }
    return 1
}


## usercancel
proc tmcquiz_usercancel {nick host handle arg} {
    global quizstate usergame theq userqnumber userqlist
    mx_log "--- Usercancel requested by $nick."
    if {$quizstate == "asked" && $usergame == 1} {
	if {[info exists theq(Author)] && ![mx_str_ieq $nick $theq(Author)]} {
	    mxirc_notc $nick "No, only $theq(Author) can cancel this question!"
	} else {
	    mxirc_notc $nick "Your question is canceled and will be solved."
	    set theq(Comment) "canceled by user"
	    tmcquiz_solve "user canceling" 0 {}
	}
    } elseif {[mx_userquests_available]} {
	array set aq [lindex $userqlist $userqnumber]
	if {[mx_str_ieq $aq(Author) $nick]} {
	    mxirc_notc $nick "Your question \"$aq(Question)\" will be skipped."
	    set aq(Comment) "canceled by user"
  	    set userqlist [lreplace $userqlist $userqnumber $userqnumber [array get aq]]
	    incr userqnumber
	} else {
	    mxirc_notc $nick "Sorry, the next question is by $aq(Author)."
	}
    } else {
	mxirc_notc $nick "No usergame running or ahead."
    }
    return 1
}


## pubm help wrapper
proc tmcquiz_pub_help {nick host handle channel arg} {
    global quizconf

    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
	return 0
    } else {
	tmcquiz_help $nick $host $handle $arg
	return 1
    }
}


## pubm !nilai to report scores
proc tmcquiz_pub_score {nick host handle channel arg} {
    global allstarsarray userlist
    global quizconf

    variable allstarspos
    variable pos 0
    variable self_has "You have"
    variable self_is "You are"
    variable self "You"
    variable target

    set arg [string trim "$arg"]

    if {$arg != "" && $arg != $nick} {
	set self_has "$arg has"
	set self_is "$arg is"
	set self "$arg"
	set target $arg
    } else {
	set target $nick
    }


    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
	return 0
    } else {
	# report rank entries
	if {[info exists userlist($target)]} {
	    array set userarray $userlist($target)
	    if {$userarray(score)} {
		# calc position
		set pos [mx_get_rank_pos $target]
		mxirc_say $channel "4,1D15,1ik 11,1$nick0,1 you have 4 $userarray(score) 0,1points and you are in ranking 4 $pos "
	    } else {
		mxirc_say $channel "0,1$nick you don't have point in this game."
	    }
	} else {
	    mxirc_say $channel "0,1$nick hasn't registered in this game."
	}

	# report allstars entries
	if {[mx_get_allstars_pos $target]} {
#	    mxirc_notc $nick "$self accumulated [format "\%5.3f" [lindex $allstarsarray($target) 1]] points in the all-stars table and keep position [mx_get_allstars_pos $target] after [lindex $allstarsarray($target) 0] games."
	} else {
#	    mxirc_notc $nick "$self_has not yet reached the all-stars table."
	}

	return 1
    }
}


## help
proc tmcquiz_help {nick host handle arg} {
    global botnick version_tmcquizz
    global quizconf quizhelp quizshorthelp
    global funstuff_enabled

    variable lines
    variable help
    variable topics [array names quizhelp]

    set arg [string tolower [string trim $arg]]

    
    # choose help text
    mx_log "--- help requested by $nick about '$arg'"

    # elide some help text based on configuration
    if {$quizconf(userquestions) == "no"} {
	set index [lsearch $topics "userquestions"]
	set topics [lreplace $topics $index $index]
    }
    
    if {![info exists funstuff_enabled] || $funstuff_enabled != 1} {
	set index [lsearch $topics "fun"]
	set topics [lreplace $topics $index $index]
    }

    
    # select help text
    if {$arg == ""} {
	set lines [format $quizshorthelp $topics]
    } else {
	if {[lsearch $topics $arg] != -1} {
	    set lines $quizhelp($arg)
	} else {
	    set lines [list "Can't help you about '$arg'.  Choose a topic from: $topics."]
	}
    }

    
    # dump help
    mxirc_notc $nick "Help for $botnick version $version_tmcquizz" 
    foreach line $lines {
	mxirc_notc $nick $line
    }
    
    return 1
}


## skipuserquest  -- removes a scheduled userquest
proc tmcquiz_skipuserquest {handle idx arg} {
    global userqnumber userqlist
    if {[mx_userquests_available]} {
	mxirc_dcc $idx "Skipping the userquest [lindex $userqlist $userqnumber]"
	incr userqnumber
    } else {
	mxirc_dcc $idx "No usergame scheduled."
    }
    return 1
}


## saveuserquest  -- append all asked user questions to $userqfile
proc tmcquiz_saveuserquests {handle idx arg} {
    global userqfile userqlist userqnumber
    variable uptonum $userqnumber
    array set aq ""

    if {[llength $userqlist] == 0 || ($userqnumber == 0 && $arg == "")} {
	mxirc_dcc $idx "No user questions to save."
    } else {
	# save all questions?
	if {[string tolower [string trim $arg]] == "all"} {
	    set uptonum [llength $userqlist]
	}

	mx_log "--- Saving userquestions ..."
	if {[file exists $userqfile] && ![file writable $userqfile]} {
	    mxirc_dcc $idx "Cannot save user questions to \"$userqfile\"."
	    mx_log "--- Saving userquestions ... failed."
	} else {
	    set fd [open $userqfile a+]
	    ## assumes, that userqlist is correct!!
	    for {set anum 0} {$anum < $uptonum} {incr anum} {
		set q [lindex $userqlist $anum]
		# clear old values
		foreach val [array names aq] {
		    unset aq($val)
		}
		array set aq $q
		
		# write some first elements
		foreach n [list "Question" "Answer" "Regexp"] {
		    if {[info exists aq($n)]} {
			puts $fd "$n: $aq($n)"
			unset aq($n)
		    }
		}
		
		# spit the rest
		foreach n [lsort -dictionary [array names aq]] {
		    puts $fd "$n: $aq($n)"
		}
		puts $fd ""
	    }
	    close $fd

	    # prune saved and asked questions
	    for {set i 0} {$i < $userqnumber} {incr i} {
		set userqlist [lreplace $userqlist 0 0]
	    }

	    mxirc_dcc $idx "Saved $userqnumber user questions."
	    mx_log "--- Saving userquestions ... done"

	    ## reset userqnumber
	    set userqnumber 0
	}
    }
    return 1
}


## set score of open question
proc tmcquiz_set_score {handle idx arg} {
    ## [pending] obeye state!
    global quizstate theq banner
    global quizconf

    mx_log "--- set_score by $handle: $arg"
    if {![regexp {^[0-9]+$} $arg]} {
	mxirc_dcc $idx "$arg not a valid number."
    } elseif {$arg == $theq(Score)} {
	mxirc_dcc $idx "New score is same as old score."
    } else {
	mxirc_dcc $idx "Setting score for the question to $arg points ([format "\%+d" [expr $arg - $theq(Score)]])."
	mxirc_say $quizconf(quizchannel) "Setting score for the question to $arg points ([format "\%+d" [expr $arg - $theq(Score)]])."
	set theq(Score) $arg
    }
    return 1
}

###########################################################################
#
# commands to manage the rankings
#
###########################################################################

## read ranks from $rankfile
proc tmcquiz_rank_load {handle idx arg} {
    global rankfile userlist timerankreset
    global lastsolver lastsolvercount qnum_thisgame
    global quizconf
    variable timeranksaved [unixtime]
    variable fd
    variable line
    variable us 0 sc 0 mask ""

    ## clear old userlist (ranks)
    foreach u [array names userlist] {
	unset userlist($u)
    }

    ## load saved scores
    if {[file exists $rankfile] && [file readable $rankfile]} {
	set fd [open $rankfile r]
	while {![eof $fd]} {
	    set line [gets $fd]
	    if {![regexp "#.*" $line]} {
		switch -regexp $line {
		    "^winscore: .+ *$" {
			scan $line "winscore: \%d" quizconf(winscore)
		    }
		    "^rankreset: +[0-9]+ *$" {
			scan $line "rankreset: \%d" timerankreset
		    }
		    "^lastsolver:" {
			scan $line "lastsolver: \%s = \%d" lastsolver lastsolvercount
		    }
		    "^ranksave:" {
			scan $line "ranksave: \%d" timeranksaved
		    }
		    "^qnumber:" {
			scan $line "qnumber: \%d" qnum_thisgame
		    }
		    default {
			scan $line "\%d \%d : \%s at \%s" started sc us mask
			set alist [list "mask" $mask "score" $sc "lastspoken" 0 "started" [expr $started + [unixtime] - $timeranksaved]]
			set userlist($us) $alist
		    }
		}
	    }
	}
	close $fd
	mxirc_dcc $idx "Ranks loaded ([llength [array names userlist]]), winscore = $quizconf(winscore), saved at unixtime $timeranksaved."
	mx_log "--- Ranks loaded ([llength [array names userlist]]), winscore = $quizconf(winscore), saved at unixtime $timeranksaved."
    } else {
	mxirc_dcc $idx "Could not read \"$rankfile\"."
	mx_log "---- could not read \"$rankfile\"."
    }
    return 1
}


## save ranks to $rankfile
proc tmcquiz_rank_save {handle idx arg} {
    global rankfile userlist
    global lastsolver lastsolvercount timerankreset
    global qnum_thisgame
    global quizconf
    variable fd

    ## save ranks
    if {[llength [array names userlist]] > 0} {
	set fd [open $rankfile w]
	puts $fd "# rankings from $quizconf(quizchannel) at [ctime [unixtime]]."
	puts $fd "winscore: $quizconf(winscore)"
	puts $fd "rankreset: $timerankreset"
	puts $fd "ranksave: [unixtime]"
	puts $fd "qnumber: $qnum_thisgame"
	if {$lastsolver != ""} {
	    puts $fd "lastsolver: $lastsolver = $lastsolvercount"
	}
	foreach u [lsort -command mx_sortrank [array names userlist]] {
	    array set aa $userlist($u)
	    puts $fd [format "\%d \%d : \%s at \%s" $aa(started) $aa(score) $u $aa(mask)]
	}
	close $fd
	mx_log "--- Ranks saved to \"$rankfile\"."
	mxirc_dcc $idx "Ranks saved to \"$rankfile\"."
    } else {
	mxirc_dcc $idx "Ranks are empty, nothing saved."
    }
    return 1
}

## set score of a player
proc tmcquiz_rank_set {handle idx arg} {
    global userlist botnick
    global quizconf
    variable list
    variable user "" newscore 0 oldscore 0
#    variable prefix

    ## called directly?
#    if {[info level] != 1} {
#	set prefix
#    } else {
#	set prefix
#    }

    mx_log "--- rankset requested by $handle: $arg"

    set list [split $arg]
    for {set i 0} {$i < [llength $list]} {incr i 2} {
	set user [lindex $list $i]
	set newscore [lindex $list [expr 1 + $i]]
	if {($newscore == "") || ($user == "")} {
	    mxirc_dcc $idx "Wrong number of parameters.  Cannot set \"$user\" to \"$newscore\"."
	} elseif {[regexp {^[\+\-]?[0-9]+$} $newscore] == 0} {
	    mxirc_dcc $idx "$newscore is not a number.  Ignoring set for $user."
	} else {
	    if {![info exists userlist($user)]} {
		if {[onchan $user $quizconf(quizchannel)]} {
		    mx_getcreate_userentry $user [getchanhost $user $quizconf(quizchannel)]
		} else {
		    mxirc_dcc $idx "Could not set rank for $user.  Not in list nor in quizchannel."
		    continue
		}
	    }
	    array set aa $userlist($user)
	    set oldscore $aa(score)
	    if {[regexp {^[\+\-][0-9]+$} $newscore]} {
		set newscore [expr $oldscore + $newscore]
		if {$newscore < 0} {
		    mxirc_dcc $idx "You set the score of $user to $newscore.  Will be corrected to 0."
		    set newscore 0
		}
	    }
	    set aa(score) $newscore
	    set userlist($user) [array get aa]
	    ## did we change something?
	    if {[expr $newscore - $oldscore] != 0} {
		set txt "0,1$user gets extra score 4 [format "\%+d" [expr $newscore - $oldscore]] 0,1 ToTal Nilai 4 $newscore points 0,1 peringkat ke [mx_get_rank_pos $user]."
#		set prefix
		if {![mx_str_ieq $handle $botnick] && [hand2nick $handle] != ""} {
		    set txt "$txt  Set by [hand2nick $handle]."
		}
		mxirc_say $quizconf(quizchannel) $txt
	    }

#	    mxirc_dcc $idx "$user has new score $newscore ([format "\%+d" [expr $newscore - $oldscore]]) peringkat ke [mx_get_rank_pos $user]."
	} 
    }
    return 1
}


## delete a player from rank
proc tmcquiz_rank_delete {handle idx arg} {
    global userlist

    mx_log "--- rank delete requested by $handle: $arg"

    if {$arg == ""} {
	mxirc_dcc $idx "Tell me whom to delete."
    } else {
	foreach u [split $arg " "] {
	    if {[info exists userlist($u)]} {
		array set aa $userlist($u)
		mxirc_dcc $idx "Nick $u removed from ranks.  Score was $aa(score) points."
		unset userlist($u)
	    } else {
		mxirc_dcc $idx "Nick $u not in ranks."
	    }
	}
    }
    return 1
}


## list ranks by notice to a user
proc tmcquiz_pub_rank {nick host handle channel arg} {
    set arg [string trim $arg]
    if {$arg == ""} {
	set arg 10
    } elseif {![regexp "^\[0-9\]+$" $arg]} {
	mxirc_notc $nick "Sorry, \"$arg\" is not an acccepted number."
	return
    }
#    mx_spit_rank "NOTC" $nick $arg
    mx_spit_rank "NOTC" $channel $arg
}

## show rankings
proc tmcquiz_rank {handle idx arg} {
    global quizconf

    set arg [string trim $arg]
    if {$arg == ""} {
	set arg 5
    } elseif {![regexp "^\[0-9\]+$" $arg]} {
	mxirc_dcc $idx "Sorry, \"$arg\" is not an acccepted number."
    }
    mx_spit_rank "CHANNEL" $quizconf(quizchannel) $arg
}

## function to show the rank to a nick or channel
proc mx_spit_rank {how where length} {
    global timerankreset
    global userlist
    global quizconf
    variable pos 1
    variable prevscore 0
    variable entries 0
    variable lines ""

    # anybody with a point?
    foreach u [array names userlist] {
	array set aa $userlist($u)
	if {$aa(score) > 0} {
	    set entries 1
	    break
	}
    }

    # build list
    if {$entries == 0} {
	lappend lines "Highscore list is empty."
    } else {
	if {$length > $quizconf(maxranklines)} {
	    set length $quizconf(maxranklines)
#	    lappend lines "Your requested too many lines, limiting to
$quizconf(maxranklines)."
	}
	lappend lines "0,14Highscore Current Top $length:"
	set pos 1
	set prevscore 0
	foreach u [lsort -command mx_sortrank [array names userlist]] {
	    array set aa $userlist($u)
	    if {$aa(score) == 0} { break }
	    if {$pos > $length && $aa(score) != $prevscore} { break }

	    if {$aa(score) == $prevscore} {
		set text "0,15= " 
	    } else {
		set text [format "0,15\%2d " $pos]
	    }
	    set text [format "0,15$text \%12s :: \%5d pts." $u $aa(score)]
	    if {$pos == 1} {
		set text "1,15$text 4* Congrats! *1,15"
	    }
	    lappend lines $text
	    set prevscore $aa(score)
	    incr pos
	}
	lappend lines "6,15Rank started7,15 [mx_duration $timerankreset] ago."
    }

    # spit lines
    foreach line $lines {
	if {$how == "NOTC"} {
	    mxirc_say $where $line
#	    mxirc_notc $where $line

	} else {
	    mxirc_say $where $line
	}
    }

    return 1
}


## reset rankings
#proc tmcquiz_rank_reset {handle idx arg} {
#    global timerankreset userlist lastsolver lastsolvercount
#    global quizconf qnum_thisgame aftergame
#
#    ## called directly?
#    if {[info level] != 1} {
#	set prefix 
#    } else {
#	set prefix 
#    }
#
#    # forget last solver
#    set lastsolver ""
#    set lastsolvercount 0
#
#    # clear userlist
#    foreach u [array names userlist] {
#	unset userlist($u)
#    }
#    set timerankreset [unixtime]
#    mxirc_say $quizconf(quizchannel) "$prefix [botcolor boldtxt]Ranks reseted by $handle after $qnum_thisgame questions."
#    mxirc_dcc $idx "Ranks are resetted.  Note that the value of aftergame was neither considered nor changed."
#    set qnum_thisgame 0
#    mx_log "--- Ranks reseted by $handle at [unixtime]."
#    return 1
#}


## calculate position of nick in the rank table
proc mx_get_rank_pos {nick} {
    global userlist
    variable pos 0
    variable prevscore 0

    if {[llength [array names userlist]] == 0 || \
	![info exists userlist($nick)]} {
	return 0
    }

    # calc position
    foreach name [lsort -command mx_sortrank [array names userlist]] {
	array set afoo $userlist($name)
	if {$afoo(score) != $prevscore} {
	    incr pos
	}

	set prevscore $afoo(score)
	if {[mx_str_ieq $name $nick]} {
	    break
	}
    }
    return $pos
}


## sort routine for the rankings
proc mx_sortrank {a b} {
    global userlist
    array set aa $userlist($a)
    array set bb $userlist($b)
    if {$aa(score) == $bb(score)} {
	return 0
    } elseif {$aa(score) > $bb(score)} {
	return -1
    } else {
	return 1
    }
}


###########################################################################
#
# Commands to handle the allstars list
#
###########################################################################


## load allstars list
proc tmcquiz_allstars_load {handle idx arg} {

    global allstarsarray allstarsfile allstars_starttime
    global quizconf

    variable thismonth 0

    mx_log "--- reading allstars list from $allstarsfile"

    if {$quizconf(monthly_allstars) == "yes"} {
	set thismonth [clock scan [clock format [unixtime] -format "\%m/01/\%Y"]]
    }

    if {[file exists $allstarsfile] && [file readable $allstarsfile]} {
	# clear old list
	foreach name [array names allstarsarray] {
	    unset allstarsarray($name)
	}
	set allstars_starttime -1

	## read datafile
	set fd [open $allstarsfile r]
	while {![eof $fd]} {
	    set line [gets $fd]
	    if {![regexp "#.*" $line]} {
		if {[scan $line "\%d \%d : \%d \%d --  \%s \%s " time duration sctotal sc us usermask] == 6} {
		    if {$time >= $thismonth} {
			if {$allstars_starttime == -1} {
			    set allstars_starttime $time
			}
			if {[info exists allstarsarray($us)]} {
			    set entry $allstarsarray($us)
			    set allstarsarray($us) [list \
				    [expr [lindex $entry 0] + 1] \
				    [expr [lindex $entry 1] + [mx_allstarpoints $sctotal $sc $duration]] \
				    ]
			} else {
			    set allstarsarray($us) [list \
				    1 \
				    [mx_allstarpoints $sctotal $sc $duration] \
				    ]
			}
		    }
		} else {
		    mx_log "---- allstar line not recognized: \"$line\"."
		}
	    }
	}
	close $fd
	mx_log "---- allstars list successfully read ([llength [array names allstarsarray]] users)."
	mxirc_dcc $idx "Allstars list successfully read ([llength [array names allstarsarray]] users)."
    } else {
	mx_log  "---- could not read \"$allstarsfile\", allstars list set empty."
	mxirc_dcc $idx  "Could not read \"$allstarsfile\", allstars list set empty."
	array set allstarsarray {}
    }
    return 1
}


## list allstars by notice to a user
proc tmcquiz_pub_allstars {nick host handle channel arg} {
    set arg [string trim $arg]
    if {$arg == ""} {
	set arg 10
    } elseif {![regexp "^\[0-9\]+$" $arg]} {
	mxirc_notc $nick "Sorry, \"$arg\" is not an acccepted number."
    }
    mx_spit_allstars "NOTC" $nick $arg
}

## show allstars
proc tmcquiz_allstars {handle idx arg} {
    global quizconf

    set arg [string trim $arg]
    if {$arg == ""} {
	set arg 5
    } elseif {![regexp "^\[0-9\]+$" $arg]} {
	mxirc_dcc $idx "Sorry, \"$arg\" is not an acccepted number."
	return
    }
    mx_spit_allstars "CHANNEL" $quizconf(quizchannel) $arg
}

## show all-star rankings
proc mx_spit_allstars {how where length} {
    global allstarsarray allstars_starttime
    global quizconf
    variable score
    variable games
    variable numofgames 0
    variable lines ""

    if {[llength [array names allstarsarray]] == 0} {
	lappend lines "Allstars list is empty."
    } else {
	# limit num of lines
	if {$length > $quizconf(maxranklines)} {
	    set length $quizconf(maxranklines)
	    lappend lines "Your requested too many lines, limiting to $quizconf(maxranklines)."
	}

	# build table
	set aline "[banner] [botcolor highscore]"
	if {$quizconf(monthly_allstars) == "yes"} {
	    set aline "$aline[clock format $allstars_starttime -format \%B] "
	}
	set aline "$aline[]All-Stars top $length:"
	lappend lines $aline
	set pos 1
	set prevscore 0
	foreach u [lsort -command mx_sortallstars [array names allstarsarray]] {
	    set entry $allstarsarray($u)
	    set games [lindex $entry 0]
	    set score [lindex $entry 1]
	    incr numofgames $games

	    # if {$score == 0} { break }
	    ## continue counting num of games played!!
	    if {$pos > $length && $score != $prevscore} { continue }
	    if {$score == $prevscore} {
		set text " = " 
	    } else {
		set text [format "\%2d " $pos]
	    }
	    set text [format "$text \%18s  -- \%5.3f pts, \%2d games." $u $score $games]
	    if {$pos == 1} {
		set text "$text * Congrats! *"
	    }
	    lappend lines $text
	    set prevscore $score
	    incr pos
	}
	lappend lines "[botcolor boldtxt]There were [llength [array names allstarsarray]] users playing $numofgames games."
    }

    # spit table
    foreach line $lines {
	if {$how == "NOTC"} {
	    mxirc_notc $where $line
	} else {
	    mxirc_say $where $line
	}
    }

    return 1
}


proc mx_saveallstar {time duration score name mask} {
    global allstarsfile userlist allstarsarray allstars_starttime
    global quizconf botnick
    variable scoretotal 0
    
    # compute sum of scores in this game
    foreach u [array names userlist] {
	array set afoo $userlist($u)
	incr scoretotal $afoo(score)
    }
    
    if {$scoretotal == $score} {
#	mxirc_action $quizconf(quizchannel) "does not record you in the allstars table, since you played alone."
	mx_log "--- $name was not saved to allstars since he/she was playing alone."
    } else {
	# save entry
	set fd [open $allstarsfile a]
	puts $fd "$time $duration : $scoretotal $score -- $name $mask"
	close $fd
	if {[info exists allstarsarray($name)]} {
	    set entry $allstarsarray($name)
	    set allstarsarray($name) [list \
		    [expr [lindex $entry 0] + 1] \
		    [expr [lindex $entry 1] + [mx_allstarpoints $scoretotal $score $duration]] \
		    ]
	} else {
	    set allstarsarray($name) [list \
		    1 \
		    [mx_allstarpoints $scoretotal $score $duration] \
		    ]
	}

	# reload allstars table if a new month began
	if {$quizconf(monthly_allstars) == "yes" &&
	$allstars_starttime < [clock scan [clock format [unixtime] -format "\%m/01/\%Y"]]} {
	    mx_log "--- new month, reloading allstars table"
	    tmcquiz_allstars_load $botnick 0 {}
	}

#	mxirc_action $quizconf(quizchannel) "records $name with [format "\%5.3f" [mx_allstarpoints $scoretotal $score $duration]] points on the all-stars table (now [format "\%5.3f" [lindex $allstarsarray($name) 1]] points, pos [mx_get_allstars_pos $name])."
	mx_log "--- saved $name with [mx_allstarpoints $scoretotal $score $duration] points (now [format "\%5.3f" [lindex $allstarsarray($name) 1]] points, pos [mx_get_allstars_pos $name]) to the allstars file. Time: $time"
    }
}


## compute position of $nick in allstarsarray
proc mx_get_allstars_pos {nick} {
    global allstarsarray
    variable pos 0
    variable prevscore 0

    if {[llength [array names allstarsarray]] == 0 || \
	![info exists allstarsarray($nick)]} {
	return 0
    }

    # calc position
    foreach name [lsort -command mx_sortallstars [array names allstarsarray]] {
	if {[lindex $allstarsarray($name) 1] != $prevscore} {
	    incr pos
	}

	set prevscore [lindex $allstarsarray($name) 1]
	if {[mx_str_ieq $name $nick]} {
	    break
	}
    }
    return $pos
}


## calculate entry in allstar table
proc mx_allstarpoints {sum score duration} {
    if {$sum == $score} {
	return 0
    } else {
	return [expr (10 * double($sum)) / (log10($score) * $duration)]
    }
}


## sort routine for the allstars
proc mx_sortallstars {a b} {
    global allstarsarray
    variable sca [lindex $allstarsarray($a) 1]
    variable scb [lindex $allstarsarray($b) 1]

    if {$sca == $scb} {
	return 0
    } elseif {$sca > $scb} {
	return -1
    } else {
	return 1
    }
}


###########################################################################
#
# User comment handling
#
###########################################################################

## record a comment
proc tmcquiz_pub_comment {nick host handle channel arg} {
    global commentsfile

    set arg [string trim $arg]
    if {$arg != ""} {
	set fd [open $commentsfile a]
	puts $fd "\[[ctime [unixtime]]\] $nick on $channel comments: $arg"
	close $fd

	mxirc_notc $nick "Your comment was logged."
    } else {
	mxirc_notc $nick "Well, comment something *g*"
    }
}

## record a comment
proc tmcquiz_dcc_comment {handle idx arg} {
    global commentsfile

    set arg [string trim $arg]
    if {$arg != ""} {
	set fd [open $commentsfile a]
	puts $fd "\[[ctime [unixtime]]\] $handle comments: $arg"
	close $fd

	mxirc_dcc $idx "Your comment was logged."
    } else {
	mxirc_dcc $idx "Well, comment something *g*"
    }
}

###########################################################################
#
# Configuration file reading and writing, setup
#
###########################################################################

# public interface to set the configuration variables from the config file
proc tmcquiz_config_set {handle idx arg} {
    global quizconf
    variable key
    variable value
    variable success 0

    # collapse whitespace
    regsub -all " +" $arg " " arg

    # extract key and value
    set list [split $arg]
    for {set i 0} {$i < [llength $list]} {incr i 2} {
	set key [string tolower [lindex $list $i]]
	set value [lindex $list [expr 1 + $i]]

	# first lets see if the key exists
	set keylist [array names quizconf "*$key*"]
	if {[llength $keylist] == 1} { set key [lindex $keylist 0] }

	if {[info exists quizconf($key)]} {
	    if {$value == ""} {
		mxirc_dcc $idx "$key = $quizconf($key)"
	    } else {
		mx_log "--- config tried $key = $value"
		set success 0
		set oldvalue $quizconf($key)
		switch -regexp $oldvalue {
		    "^(yes|no)" {
			if {[regexp "^(yes|no)$" $value]} {
			    set quizconf($key) $value
			    set success 1
			}
		    }
		    "^\[0-9\]+$" {
			if {[regexp "^\[0-9\]+$" $value]} {
			    set quizconf($key) $value
			    set success 1
			}
		    }
		    default {
			set quizconf($key) $value
			set success 1
		    }
		}
		
		if {$success == 1} {
		    mxirc_dcc $idx "Config $key successfully set to $value."
		    mx_log "-- config $key set to $value."
		    
		    # action on certain variables
		    mx_config_apply $key $oldvalue

		} else {
		    mxirc_dcc $idx "Config $key could not be set to '$value', wrong format."
		    mx_log "-- Config $key could not be set to '$value', wrong format."
		}
		set success 0
	    }
	} else {
	    # dump keys with substring
	    set keylist [array names quizconf "*$key*"]
	    if {[llength $keylist] == 0} {
		mxirc_dcc $idx "Sorry, no configuration matches '$key'"
	    } else {
		mxirc_dcc $idx "Matched configuration settings for '$key':"
		for {set j 0} {$j < [llength $keylist]} {incr j 1} {
		    mxirc_dcc $idx "[lindex $keylist $j] = $quizconf([lindex $keylist $j])"
		}
	    }
	}
    }

    # check if arg was empty and dump _all_ known keys then
    if {$arg == ""} {
	set keylist [array names quizconf]
	mxirc_dcc $idx "Listing all settings:"
	for {set j 0} {$j < [llength $keylist]} {incr j 1} {
	    mxirc_dcc $idx "[lindex $keylist $j] = $quizconf([lindex $keylist $j])"
	}
    }

    return 1
}


# public interface for readconfig
proc tmcquiz_config_load {handle idx arg} {
    global configfile
    mxirc_dcc $idx "Loaded [mx_config_read $configfile] configuration entries."
}


# public interface for readconfig
proc tmcquiz_config_save {handle idx arg} {
    global configfile
    mxirc_dcc $idx "Saved [mx_config_write $configfile] configuration entries."
}

# applies a configuration and makes neccessary setup
proc mx_config_apply {key oldvalue} {
    global whisperprefix quizconf channelrules botnick pricesfile
    variable value $quizconf($key)

    switch -exact $key {
	"winscore" {
	    if {$oldvalue != {}} {
#		mxirc_say $quizconf(quizchannel) "Score to win set from $oldvalue to $value ([format "\%+d" [expr $value - $oldvalue]])."
	    }
	}
	"msgwhisper" {
	    if {$value == "yes"} {
		set whisperprefix "PRIVMSG"
	    } else {
		set whisperprefix "NOTICE"
	    }
	}
	"channelrules" {
	    if {$value == "yes"} {
		bind msg - !rules tmcquiz_rules
		bind pub - !rules tmcquiz_pub_rules
		tmcquiz_rules_read $botnick 0 {}
	    } else {
		unbind msg - !rules tmcquiz_rules
		unbind pub - !rules tmcquiz_pub_rules
	    }
	}
        "prices" {
            if {$value == "yes"} {
                mx_read_prices $pricesfile
            }
        }
    }
}

# reads configuration from cfile into global variable quizconf
proc mx_config_read {cfile} {
    global quizconf

    variable num 0

    mx_log "--- Loading configuration from $cfile ..."

    set fd [open $cfile r]
    while {![eof $fd]} {
	gets $fd line
	if {![regexp "^ *#.*$" $line] && ![regexp "^ *$" $line]} {
	    set content [split $line {=}]
	    set key [string trim [lindex $content 0]]
	    set value [string trim [lindex $content 1]]
	    set quizconf($key) $value
	    incr num
	}
    }
    close $fd

    mx_log "--- Configuration loaded: $num settings."

    foreach $key [array names quizconf] {
	mx_config_apply $key {}
    }

    return $num
}


# writes configuration from global quizconf to cfile
proc mx_config_write {cfile} {
    global quizconf

    variable num 0
    variable written ""

    mx_log "--- Saving configuration to $cfile ..."


    set fdin [open $cfile r]
    set fdout [open "$cfile.tmp" w]

    # replace known configs
    while {![eof $fdin]} {
	gets $fdin line
	switch -regexp $line {
	    "(^ *$|^ *#.*$)" {
		puts $fdout $line
	    }
	    "^(.*)=(.*)$" {
		set content [split $line {=}]
		set key [string trim [lindex $content 0]]
		set value [string trim [lindex $content 1]]
		if {[info exists quizconf([string trim $key])]} {
		    puts $fdout "$key = $quizconf([string trim $key])"
		    incr num
		} else {
		    puts $fdout $line
		}
		lappend written [string trim $key]
	    }
	}
    }

    # append "new" configs not mentioned in the file
    puts $fdout "###########################################################################"
    puts $fdout "###########################################################################"
    puts $fdout "#"
    puts $fdout "# New Configuration values."
    puts $fdout "#"

    set keys [array names quizconf]
    for {set i 0} {$i < [llength $keys]} {incr i} {
	set key [lindex $keys $i]
	if {[lsearch -exact $written $key] == -1} {
	    puts $fdout "$key = $quizconf($key)"
	}
    }

    close $fdin
    close $fdout

    # delete old config
    file rename -force "$cfile.tmp" $cfile

    mx_log "--- Configuration saved: $num settings."
    return $num
}

###########################################################################
#
# Handling of certain events, like +m, nickchanges and others
#
###########################################################################

## player has changed nick.  Adjust ranking
## proc tmcquiz_on_nickchanged {nick host handle channel newnick} {
##    global userlist quizconf
##    variable addscore 0 ascore 0

##    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
##	return
##    }
    # see, if we can collect some old scores
##    if {[info exists userlist($newnick)]} {
##	mx_log "---- old entry: $userlist($newnick)"
	# check if we know the host
##	array set oldentry $userlist($newnick)
##	if {$oldentry(mask) == [maskhost $host]} {
##	    set addscore $oldentry(score)
##	    if {$addscore != 0} {
##		mxirc_notc $newnick "Your old scores of $addscore were collected, since I know your host."
##	    }
##	} else {
##	    set addscore 0
##	    if {$oldentry(score) != 0} {
##		mxirc_notc $newnick "Your host was new to me, old scores deleted."
##	    }
##	}
	
	# new score gathered, sum up!
##	if {[info exists userlist($nick)]} {
##	    array set newentry $userlist($nick)
##	    incr newentry(score) $addscore
##	    set newentry(started) $oldentry(started)
##	    set userlist($newnick) [array get newentry]
##	    unset userlist($nick)
##	}
	# else, the newnick is still in ranks.
	# do nothing.

##    } elseif {[info exists userlist($nick)]} {
##	set userlist($newnick) $userlist($nick)
##	array set afoo $userlist($newnick)
##	unset userlist($nick)
##	mx_log "---- Known, scores transferred."
##	if {$afoo(score) > 0} {
##	    mxirc_notc $newnick "I saw you changed you nick and transferred scores."
##	}
##    }
##}


## notification when a user joins in
##proc tmcquiz_on_joined {nick host handle channel} {
##    global qlist version_tmcquizz userlist
##    global quizconf quizstate qnum_thisgame
    
##    variable text

 ##   if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
##	return
 ##   }
    
##    set text "Welcome, type \"!play\" to start the scramble game"

##    if {$quizconf(channelrules) == "yes"} {
##	set text "$text"
##    }

##    mxirc_notc $nick $text
##    if {[info exists userlist($nick)]} {
##	array set aa $userlist($nick)
##	if {$aa(mask) == [maskhost $host]} {
##	    if {$aa(score) > 0} {
##		mxirc_notc $nick "jangan ganti ganti ident (host) kamu atau ranking dan score kamu akan direset jadi Nol."
##	    }
##	} else {
##	    mxirc_notc $nick "Your hostmask is new to me, deleting old scores."
##	    unset userlist($nick)
##	}
##    }

 ##   if {$quizstate == "paused"} {
##	mxirc_notc $nick "Scramble game is paused setelah $qnum_thisgame questions."
##    } elseif {$qnum_thisgame != 0} {
##	mxirc_notc $nick "Scramble game sudah berjalan, $qnum_thisgame pertanyaan."
 ##   }

 ##   mx_log "--- $nick joined the channel."
##}


###########################################################################
#
# miscellaneous tmcquiz commands not fitting elsewhere
#
###########################################################################


# say Hi to "known" users
proc tmcquiz_pub_hi {nick host handle channel arg} {
    global quizconf allstarsarray
    if {[mx_str_ieq $channel $quizconf(quizchannel)] && 
    ([validuser $handle] || [info exists allstarsarray($nick)]) } {
	mxirc_say $quizconf(quizchannel) "Hi $nick!  Nice to see you."
    }
}



###########################################################################
###########################################################################
##
## internal routines
##
###########################################################################
###########################################################################

## ----------------------------------------------------------------------
## react on certain events
## ----------------------------------------------------------------------

proc mx_event {type} {
    global botnick quizstate 
    switch -exact $type {
	"prerehash" {
	    mx_log "--- Preparing for rehashing"
	    if {$quizstate != "halted"} {
		t