##
### moxquizz.tcl -- quizzbot for eggdrop 1.4.4
## Version:
set version_tmcquizz "8.0.3"

###########################################################################
##
## ATTENTION:
##
## Defaults for bot configuration.  Don't edit here, edit the file
## quiz.rc instead!
##
###########################################################################


# system stuff
variable quizbasedir        eggdrop/scripts/
variable gamedata           eggdrop/scripts/
variable datadir            $quizbasedir/
variable configfile         $quizbasedir/quiz.rc
variable rankfile           $datadir/rank.data
variable userqfile          $datadir/questions.user.en

#
# Configuration map
#
variable quizconf

set quizconf(quizchannel)        "#Lombok"
set quizconf(quizloglevel)       1

# several global numbers
set quizconf(maxranklines)       5
set quizconf(tipcycle)           0
set quizconf(useractivetime)     240
set quizconf(userqbufferlength)  5
#set quizconf(winscore)           30

# timer delays in seconds
set quizconf(askdelay)           5
set quizconf(tipdelay)           30

# safety features and other configs
set quizconf(lastwinner_restriction)  no
set quizconf(lastwinner_max_games)    2
set quizconf(colorize)                yes
set quizconf(pausemoderated)          no
set quizconf(userquestions)           yes
set quizconf(msgwhisper)              no
set quizconf(stripumlauts)            no

##
###########################################################################
##
## stuff for the game state
##
# values = nostopped, paused, asked, waittoask, halted
variable quizstate "halted"
variable statepaused ""
variable statemoderated ""
variable usergame 0
variable timeasked [unixtime]
variable revoltmax 0
# values = newgame, nostop, halt, exit
variable aftergame "newgame"

#
# variables for the ranks and user handling
#
variable timerankreset [unixtime]
variable userlist
variable revoltlist ""
variable lastsolver ""
variable lastsolvercount 0
variable lastwinner ""
variable lastwinnercount 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 - !starcfk tmcquiz_ask
bind pub - !savecfk tmcquiz_saves
bind pub - !nostop tmcquiz_nostops
bind pub m !exit tmcquiz_exit
bind msg m !addquest tmcquiz_userquest
bind msg m !usercancel tmcquiz_usercancel
bind msg m !usersolve tmcquiz_usersolve
bind pub m !qhelp tmcquiz_pub_help
bind pub - !score tmcquiz_pub_score
bind pub - !rank 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

###########################################################################
#
# Helptext
#
###########################################################################
set quizshorthelp [list \
        "© by Kht@DALnet crew" \
        "Most important commands are: !start, !rank, !score and !nostop" \
        "To learn more, specify a topic: !qhelp <topic>." \
        "Topics are: %s"]


###########################################################################
#
# bot running commands
#
###########################################################################

## nostop
## nostop everything and kill all timers
proc tmcquiz_nostop {handle idx arg} {
    global quizstate
    global quizconf
    variable t
    variable prefix


    ## called directly?
    if {[info level] != 1} {
        set prefix
    } else {
        set prefix
    }

    set quizstate "nostopped"

    ## 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 nostopped."
    mxirc_say $quizconf(quizchannel) "weLeh ScrambLe nostopped."
    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) "\0036Game halted. Type \0034!start \0036for new questions."
    return 1
}


## reload questions
proc tmcquiz_reload {handle idx arg} {
    global qlist quizconf
    global datadir gamedata

    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) "\0030,1I am leaving now."
    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
    global quizconf

    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 $nick
    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 $nick] $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 $nick] $lastsolver]} {
                incr lastsolvercount
            } else {
                set lastsolver [maskhost $nick]
                set lastsolvercount 1
            }

#           ## ignore games_max in a row winner
#           if {[mx_str_ieq [maskhost $nick] $lastwinner]
#           && $lastwinnercount >= $quizconf(lastwinner_max_games)
#           && $quizconf(lastwinner_restriction) == "yes"} {
#               return
#           }

#         ##with or without bonus
          set bonuse "[get_bonuses]"
            if {$lastsolvercount == 1} {
            set addscored "$theq(Score)"
            } elseif {$lastsolvercount >= 2} {
              set addscored "[expr $bonuse + $theq(Score)]"
            }

            ## save score (set started time to time of first point)
            incr userarray(score) $addscored
            if {$userarray(score) == 1} {
                set userarray(started) [unixtime]
            }
            set userlist($nick) [array get userarray]

            ## tell channel, that the question is solved
            mx_log "--- solved after $duration by $nick with \"$text\", now has $userarray(score) points"

            regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
            mxirc_say $channel "\0032Jawaban Betul :\0036 $answer \0032by\0036 $nick \0032after \0036 $duration - \0032Score\0036 +$theq(Score) \0032Points - Total Score:\0036 $userarray(score) \0032Points - Rank:\0036 [mx_get_rank_pos $nick]"
            ## honor good games!
            if {$lastsolvercount == 1} {
            tmcquiz_rank_set $botnick 0 "$nick +0"
            mxirc_say $channel ""
#            pushmode $channel +v $nick
            } elseif {$lastsolvercount >= 2} {
            set greeting "[get_grated]"
                tmcquiz_rank_set $botnick 0 "$nick +0"
            mxirc_say $channel ""
#            pushmode $channel +v $nick

            }

            ## 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)


#                   mxirc_say $channel "$nick reaches $quizconf(winscore) points and wins$price"
                    set now [unixtime]
                    if {[mx_str_ieq [maskhost $nick] $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 nostop answer bots."
                        }
                    } else {
                        set lastwinner [maskhost $nick]
                        set lastwinnercount 1
                    }

                # 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 "\0036$nick solved his own question after \00312\037 $duration \037\0036 and has no points, keeping \00312\037 $userarray(score) \037\0036 points on rank \00312 mx_get_rank_pos $nick\0036 The Answer was: \00312\037 $answer \037"

            # remove area of tip generation tags
            regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
#           mxirc_say $channel "The answer was: $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
#       }
#       "nostopped" {
#           mxirc_notc $nick "Game is nostopped."
#           return 1
#       }
#    }

    ## record that $nick spoke (prevents desert detection from nostopping,
    ## when an user joins and starts the game with !ask)
#    if {![mx_str_ieq $nick $botnick]} {
#       mx_getcreate_userentry $nick $nick
#    }

    ## 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 "Please stand by, the next question comes in less than $quizconf(askdelay) seconds."
    } 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) 20
        }

        ## 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 "\0030,2 :: Question no. $qnumber\/2632 \0038\[\00311 ®ScrambLe® \0038\] \0030tpi@DALnet :: \003"
        mxirc_say $channel $txt
        set txt ""
        set txt "\002\037\0032Hint\002\037: $txt$theq(Question)"
        set ans "\002\037\0032Word\002\037: [randomanswer $answer]"
        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 $nick
        }

#       ## 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 "\0036sees 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 "\0030,1will solve the question immediately."
            tmcquiz_solve $botnick 0 {}
        }
    }
}


## solve question
proc tmcquiz_solve {handle idx arg} {
    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 "\0032Times Up : "
            set solver ""
        } else {
            set txt "\0030,1Manually"
            set solver "by $handle"
        }
        regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
        set txt "\0036$txt $answer "
        mxirc_say $quizconf(quizchannel) $txt

        # remove area of tip generation tags
#       regsub -all "\#(\[^\#\]*\)\#" $theq(Answer) "\\1" answer
#       mxirc_say $quizconf(quizchannel) "\00312 The 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 $nick]" "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
}



## 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 $nick $handle $arg
        return 1
    }
}


## pubm !score to report scores
proc tmcquiz_pub_score {nick host handle channel arg} {
    global userlist
    global quizconf

    variable pos 0
    variable target
    variable self 0

    if {![mx_str_ieq $channel $quizconf(quizchannel)]} {
        return 0
    } else {
        set arg [string trim "$arg"]

        if {$arg != "" && $arg != $nick} {
            set target $arg
        } else {
            set target $nick
            set self 1
        }

        # 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]
                if {$self} {
                    mxirc_say $channel "\0036 $nick,\0032kamu punya nilai \0036 $userarray(score) \0032Points. Rank:\0036 $pos"
                } else {
                    mxirc_say $channel  "\0036 $nick, \0032teman kamu\0036 $target \0032punya\0036 $userarray(score) \0032points. Rank:\0036 $pos"
                }
            } else {
                if {$self} {
                    mxirc_say $channel "\0036 $nick, \0032Score kamu masih kosong"
                } else {
                    mxirc_say $channel "\0036 $target \0032masih kosong tuh"
                }
            }
        } else {
            if {$self} {
#                mxirc_say $channel "\0032Wewwww, You are not yet listed for the current game."
            } else {
#                mxirc_say $channel "\0032Wewwww, $target \0036is not yet listed for the current game."
            }
        }


        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

    ## 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 {
            default {
                        scan $line "%s %d" us sc
                        set alist [list "score" $sc]
                        set userlist($us) $alist
                    }
                }
            }
        }
        close $fd
        mxirc_dcc $idx "Ranks loaded ([llength [array names userlist]])"
        mx_log "--- Ranks loaded ([llength [array names userlist]])"
    } else {
        mxirc_dcc $idx "Could not read \"$rankfile\"."
        mx_log "---- could not read \"$rankfile\"."
    }
    return 1
}


## save ranks to $rankfile normally
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]
        foreach u [lsort -command mx_sortrank [array names userlist]] {
            array set aa $userlist($u)
            puts $fd [format "%s %d" $u $aa(score)]
        }
        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
}

## save ranks to $rankfile as requested
proc tmcquiz_rank_saver {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]
        foreach u [lsort -command mx_sortrank [array names userlist]] {
            array set aa $userlist($u)
            puts $fd [format "%s %d" $u $aa(score)]
        }
        close $fd
        mx_log "--- Ranks saved to \"$rankfile\"."
        mxirc_dcc $idx "Ranks saved to \"$rankfile\"."
      mxirc_say $quizconf(quizchannel) "\0036Don`t worry, \00312Your\0036 score has been saved!"
    } 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 "\0036 $user \0032has repeat bonus \0036 [format "%+d" [expr $newscore - $oldscore]] \0032Points. Total \0036 $newscore \0032Points. Rank \0036 [mx_get_rank_pos $user]\003"
#               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]]) on rank [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 5
    } 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 "\0030,2..:: Tops \0038\[\00311 $length \0038\] \0030tpi Game Highscores ::..\003"
        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 "\0032= "
            } else {
                set text [format "%2d " $pos]
            }
            set text [format "\0032$text %15s :: %7d pts." $u $aa(score)]
            if {$pos == 1} {
                set text "\0032$text"
            }
            lappend lines $text
            set prevscore $aa(score)
            incr pos
        }
        lappend lines "\0030,2..:: End of Tops \0038\[\00311 $length \0038\] \0030Rank of tpi Game ::..\003"
    }

    # spit lines
    foreach line $lines {
        if {$how == "NOTC"} {
            mxirc_say $quizconf(quizchannel) $line
#           mxirc_notc $quizconf(quizchannel) $line

        } else {
            mxirc_say $quizconf(quizchannel) $line
        }
    }

    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
#
###########################################################################


###########################################################################
#
# 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 botnick
    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"
            }
        }
    }
}

# 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 "# New"

    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
#
###########################################################################


## 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
    }

#    unset text "Scrämßlé © Say \"!start\" untuk memulai"


#    mxirc_notc $nick $text
    if {[info exists userlist($nick)]} {
        array set aa $userlist($nick)
        if {$aa(mask) == [maskhost $nick]} {
            if {$aa(score) > 0} {
#               mxirc_notc $nick "Kamu sudah terdaftar dengan nilai $aa(score) points ranking [mx_get_rank_pos $nick]."
            }
        } else {
#           mxirc_notc $nick "This nick was in the data previously, so we use new score and old scores deleted."
            unset userlist($nick)
        }
    }

    mx_log "--- $nick joined the channel."
}


###########################################################################
#
# miscellaneous tmcquiz commands not fitting elsewhere
#
###########################################################################



###########################################################################
###########################################################################
##
## 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"} {
                tmcquiz_halt $botnick 0 {}
            }
            tmcquiz_rank_save $botnick 0 {}
            tmcquiz_saveuserquests $botnick 0 "all"
            tmcquiz_config_save $botnick 0 {}
            set tmp_logfiles [logfile]
            mx_log "---- will reopen logfiles: $tmp_logfiles"
            mx_log "--- Ready for rehashing"
        }
        "rehash" {
            # reopen logfiles
            mx_log "--- rehash: reloading ranks"
            tmcquiz_rank_load $botnick 0 {}
            mx_log "--- rehash: done."
        }
    }
}

## ----------------------------------------------------------------------
## mxirc ... stuff to speak
## ----------------------------------------------------------------------

## say something on quizchannel
proc mxirc_say {channel text} {
    putserv "PRIVMSG $channel :$text"
}

## say something on all channels
proc mxirc_say_everywhere {text} {
    foreach channel [channels] {
        if {[validchan $channel] && [botonchan $channel]} {
            mxirc_say $channel $text
        }
    }
}

## act on all channels
proc mxirc_action_everywhere {text} {
    foreach channel [channels] {
        if {[validchan $channel] && [botonchan $channel]} {
            mxirc_action $channel $text
        }
    }
}

## say something through another buffer
proc mxirc_quick {channel text} {
    putquick "PRIVMSG $channel :$text"
}

## act in some way (/me)
proc mxirc_action {channel text} {
    putserv "PRIVMSG $channel :\001ACTION $text\001"
}


## say something to a user
proc mxirc_msg {nick text} {
    global botnick
    if {![mx_str_ieq $botnick $nick]} {
        puthelp "PRIVMSG $nick :$text"
    }
}


## say something through another buffer
proc mxirc_quick_notc {nick text} {
    global botnick whisperprefix
    if {![mx_str_ieq $botnick $nick]} {
        putquick "$whisperprefix $nick :$text"
    }
}


## notice something to a user (whisper)
proc mxirc_notc {nick text} {
    global botnick whisperprefix
    if {![mx_str_ieq $botnick $nick]} {
        puthelp "$whisperprefix $nick :$text"
    }
}



## notice something to a user
proc mxirc_dcc {idx text} {
    if {[valididx $idx]} {
        putdcc $idx $text
    }
}


## ----------------------------------------------------------------------
## mx.... generic tool functions and internal functions
## ----------------------------------------------------------------------

## func to act according to the value of $aftergame
proc mx_aftergameaction {} {
    global botnick aftergame quizconf

    switch -exact $aftergame {
        "nostop" {
            tmcquiz_nostop $botnick 0 {}
            set aftergame "newgame"
        }
        "halt" {
            tmcquiz_halt $botnick 0 {}
            set aftergame "newgame"
        }
        "exit" {
            # sleep some milliseconds
            tmcquiz_nostop $botnick 0 {}
            mxirc_say $quizconf(quizchannel) "Thanks for playing ppl, I'll exit now (and thanks for all the fish)."
            utimer 2 mx_timer_aftergame
        }
        "newgame" {
            # do nothing special here
        }
        default {
            mx_log "ERROR: Bad aftergame-value: \"$aftergame\" -- halted"
            tmcquiz_halt $botnick 0 {}
        }
    }
}


## timer to shut the bot down from aftergameaction
proc mx_timer_aftergame {} {
    global botnick
    if {[queuesize] != 0} {
        utimer 2 mx_timer_aftergame
    } else {
        tmcquiz_exit $botnick 0 {}
    }

}

## func to log stuff
proc mx_log {text} {
    global quizconf
    putloglev $quizconf(quizloglevel) $quizconf(quizchannel) $text
}

## return a duration as a string
proc mx_duration {time} {
    return [duration [expr [unixtime] - $time]]
}

## return if strings are equal case ignored
proc mx_str_ieq {a b} {
    if {[string tolower $a] == [string tolower $b]} {
        return 1
    } else {
        return 0
    }
}

## return a mixed list of numbers from 0 ... size
proc mx_mixedlist {size} {
    variable alist "" blist ""

    for {set i 0} {$i < $size} {incr i} {
        lappend alist "$i"
    }
    set blist ""
    for {set i 0} {$i < $size} {incr i} {

        set pos [rand [llength $alist]]
        lappend blist [lindex $alist $pos]
        set alist [lreplace $alist $pos $pos]

    }
    return $blist
}



## read question data
## RETURNS:  0 if no error
##           1 if no file found
proc mx_read_questions {questionset} {
    global qlist qlistorder qnumber datadir gamedata
    variable entry
    variable tipno 0
    variable key
    variable errno 0
    # 0=out 1=in
    variable readstate 0

    mx_log "--- Loading questions."


    # keep the old questions safe
    set tmplist $qlist
    set qlist ""

    foreach datafile [glob -nocomplain "$datadir/questions*$questionset"] {

        set fd [open $datafile r]
        while {![eof $fd]} {
            set line [gets $fd]
            # an empty line terminates an entry
            if {[regexp "^ *$" $line]} {
                if {$readstate == 1} {
                    # reject crippled entries
                    if {[info exists entry(Question)]
                    && [info exists entry(Answer)]} {
                        lappend qlist [array get entry]
                    } else {
                        mx_log "[array get entry] not complete."
                    }
                    set tipno 0
                }
                set readstate 0
            } elseif {![regexp "^#.*" $line]} {
                set readstate 1
                set data [split $line {:}]
                if {![regexp "(Answer|Author|Category|Comment|Level|Question|Regexp|Score|Tip|Tipcycle)" [lindex $data 0]]} {
                    mx_log "Key [lindex $data 0] unknown!"
                } else {
                    set key [string trim [lindex $data 0]]
                    if {$key == "Tip"} {
                        set key "$key$tipno"
                        incr tipno
                    }
                    set entry($key) [string trim [join [lrange $data 1 end] ":"]]
                }
            }
        }
        close $fd

        mx_log "---- now [llength $qlist] questions, added $datafile"
    }

    if {[llength $qlist] == 0} {
        set qlist $tmplist
        mx_log "----- reset to prior questions ([llength $qlist] ones)."
        set errno 1
    }

    mx_log "--- Questions loaded."

    set qlistorder [mx_mixedlist [llength $qlist]]
    set qnumber 0
    return $errno
}


## sets back all variables when a question is solved
## and goes to state waittoask (no timer set !!)
proc mx_answered {} {
    global quizstate tipno usergame qlistfinished userqnumber
    global userqlist tiplist revoltlist revoltmax
    variable alist

    if {$quizstate == "asked"} {
        if {$usergame == 1} {
            ## save usergame
            set pos [expr $userqnumber - 1]
            set alist [lindex $userqlist $pos]
            mx_log "---- userquest stored: $alist"
            set i 0
            foreach t $tiplist {
                lappend alist "Tip$i" [lindex $tiplist $i]
                incr i
            }
            set userqlist [lreplace $userqlist $pos $pos $alist]
            ## [pending] save each question to disc
        }
        set quizstate "waittoask"
        set tipno 0
        set revoltlist ""
        set revoltmax 0

        foreach j [utimers] {
            if {[lindex $j 1] == "mx_timer_tip"} {
                killutimer [lindex $j 2]
            }
        }
    }
}


proc mx_timer_ask {} {
    global botnick quizconf
    global rankfile
    global rankfile userlist timerankreset

        #global tgcurrentanswer tghinttimer tgtimenext tgchan tgnextqtimer tgstreak tgstreakmin
        #global tgscoresbyname tgranksbyname tgranksbynum tgcongrats tgscorestotal tgmissed
        #global tgtimestart tgshowallscores tgrealnames tgscoresbyrank tgtimeanswer
      global ACAKAnswers ACAKNumAnswered ACAKQuestionTimer ACAKAdTimer ACAKRunning
      global ACAKQCount ACAKAdNumber

      tmcquiz_rank_save {} {} {}
      set ACAKQCount 0
      set ACAKAdNumber 0
      ACAK_ReadCFG
      set ACAKQCount [ACAK_ReadQuestionFile]
      set ACAKAskedFileLen [ACAK_ReadAskedFile]
      bind pubm - "*" ACAKCheckGuess
      set ACAKRunning 1
      ACAKAskQuestion
      #set tgnextqtimer [utimer $tgtimenext tgnextq]
                        #set tgplaying 1
                        #set tgstreak 0
                        #set tgmissed 0
}


## give a tip and check if channel is desert!
proc mx_timer_tip {} {
    global userlist botnick banner
    global aftergame timerankreset qnum_thisgame
    global quizconf

    variable desert 0

#    foreach u [array names userlist] {
#       array set afoo $userlist($u)
#       if {$afoo(lastspoken) >= [expr [unixtime] - ($quizconf(tipcycle) * $quizconf(tipdelay) * 2) - $quizconf(askdelay)]} {
#           set desert 0
#           break
#       }
#    }
#
    # ask at least one question
    if {$desert && $qnum_thisgame > 2} {
        mxirc_say $quizconf(quizchannel) "Channel found desert."
        mx_log "--- Channel found desert."
        tmcquiz_rank_reset $botnick {} {}
        if {$aftergame != "exit"} {
            tmcquiz_halt $botnick 0 {}
        } else {
            mx_aftergameaction
        }
    } else {
        tmcquiz_tip $botnick 0 {}
    }
}


## compare length of two elements
proc mx_cmp_length {a b} {
    variable la [string length $a] lb [string length $b]
    if {$la == $lb} {
        return 0
    } elseif {$la > $lb} {
        return 1
    } else {
        return -1
    }
}

## returns number of open userquests
proc mx_userquests_available {} {
    global userqlist userqnumber

    set num [expr [llength $userqlist] - $userqnumber]

    if {$num < 0} {
        return 0
    } else {
        return $num
    }
}


## create an entry in the userlist or update an existing
## then entry is returned
proc mx_getcreate_userentry {nick host} {
    global userlist botnick

    ## prevent myself from being added, though this will never happen
    if {[mx_str_ieq $nick $botnick]} {
        return
    }

    if {[info exists userlist($nick)]} {
        array set anarray $userlist($nick)
#       set anarray(lastspoken) [unixtime]
#       set userlist($nick) [array get anarray]
    } else {
        set userlist($nick) [list "mask" [maskhost $nick] "score" 0]
        mx_log "---- new user $nick: $userlist($nick)"
        array set anarray $userlist($nick)
    }
}

## convert some latin1 special chars
proc mx_tweak_umlauts {text} {
    regsub -all "ä" $text "ae" text
    regsub -all "ö" $text "oe" text
    regsub -all "ü" $text "ue" text
    regsub -all "Ä" $text "AE" text
    regsub -all "Ö" $text "OE" text
    regsub -all "Ü" $text "UE" text
    regsub -all "ß" $text "ss" text
    regsub -all "è" $text "e" text
    regsub -all "È" $text "E" text
    regsub -all "é" $text "e" text
    regsub -all "É" $text "E" text
    return $text
}

##################################################
#
# functions for colors
#
##################################################

## strip color codes from a string
proc mx_strip_colors {txt} {
    variable result

    regsub -all "\[\002\017\]" $txt "" result
    regsub -all "\003\[0-9\]\[0-9\]?\(,\[0-9\]\[0-9\]?\)?" $result "" result

    return $result
}

# return botcolor
proc botcolor {thing} {
    global quizconf
    if {$quizconf(colorize) != "yes"} {return ""}
#      if {$thing == "question"} {return "\003[col dblue][col uline]"}
#      if {$thing == "answer"} {return "\003[col dblue]"}
#      if {$thing == "tip"} {return "\003[col dblue]"}
    if {$thing == "question"} {return "[color dblue white][col uline]"}
    if {$thing == "answer"} {return "[color dblue white]"}
    if {$thing == "tip"} {return "[color dblue white]"}
    if {$thing == "nick"} {return "[color lightgreen black]"}
    if {$thing == "nickscore"} {return "[color lightgreen blue]"}
    if {$thing == "highscore"} {return "\003[col turqois][col uline][col bold]"}
#    if {$thing == "txt"} {return "[color red yellow]"}
    if {$thing == "txt"} {return "[color blue lightblue]"}
    if {$thing == "boldtxt"} {return "[col bold][color blue lightblue]"}
    if {$thing == "own"} {return "[color red black]"}
    if {$thing == "norm"} {return "\017"}
    if {$thing == "grats"} {return "[color purple norm]"}
    if {$thing == "score"} {return "[color blue lightblue]"}
    if {$thing == ""} {return "\003"}
}

# internal function, never used from ouside. (doesn't check colorize!)
proc color {fg bg} {
    return "\003[col $fg],[col $bg]"
}

# taken from eggdrop mailinglist archive
proc col {acolor} {
    global quizconf
    if {$quizconf(colorize) != "yes"} {return ""}

    if {$acolor == "norm"} {return "00"}
    if {$acolor == "white"} {return "00"}
    if {$acolor == "black"} {return "01"}
    if {$acolor == "blue"} {return "02"}
    if {$acolor == "green"} {return "03"}
    if {$acolor == "red"} {return "04"}
    if {$acolor == "brown"} {return "05"}
    if {$acolor == "purple"} {return "06"}
    if {$acolor == "orange"} {return "07"}
    if {$acolor == "yellow"} {return "08"}
    if {$acolor == "lightgreen"} {return "09"}
    if {$acolor == "turqois"} {return "10"}
    if {$acolor == "lightblue"} {return "11"}
    if {$acolor == "dblue"} {return "12"}
    if {$acolor == "pink"} {return "13"}
    if {$acolor == "grey"} {return "14"}


    if {$acolor == "bold"} {return "\002"}
    if {$acolor == "uline"} {return "\037"}
#    if {$color == "reverse"} {return "\022"}
}

###########################################################################

## Banner:

proc banner {} {
    return "Puzzle Game"
}

# should return as much spaces as the banner needs (for best results)
proc bannerspace {} {
    return " "
}

###########################################################################
#
# Initialize
#

mx_config_read $configfile

mx_log "**********************************************************************"
mx_log "--- $botnick started"

mx_read_questions $quizconf(questionset)
tmcquiz_rank_load $botnick 0 {}
set quizconf(quizchannel) [string tolower $quizconf(quizchannel)]
channel add $quizconf(quizchannel)

proc randomanswer {answer} {

   set lq [split $answer " "]
   set tq ""
   foreach elq $lq {
      if {[string trim $elq] != ""} {
         append tq [randstr $elq] " "
      }
   }
   return [string trim $tq]
}

proc randstr {word} {

   set l [string length $word]
   set n 0
   while {$n < $l} {
      set c [string index $word $n]
      set ch($n) $c
      incr n
   }
   set n 0
   while {$n < $l} {
      set r [rand $l]
      set t $ch($n)
      set ch($n) $ch($r)
      set ch($r) $t
      incr n
   }
   set w ""
   set n 0
   while {$n < $l} {
      append w $ch($n)
      incr n
   }
   return $w
}

## Bonus To Use
set BonusToUse {
"10"
}

## Proc to Randomly Select an Info Item!
proc get_bonuses { } {
 global BonusToUse
 set outputiz4 [lindex $BonusToUse [rand [llength $BonusToUse]]]
 return $outputiz4
}

## Conggrats To Use
set ConggratToUse {
"WoW!!"
"Nice!!"
"Good!!"
"Great!!"
"Superb!!"
"WeLLdone!!"
"CooL!!"
"KewL!!"
"WeWW!!"
}
## Proc to Randomly Select Conggrat!
proc get_grated { } {
 global ConggratToUse
 set outputiz5 [lindex $ConggratToUse [rand [llength $ConggratToUse]]]
 return $outputiz5
}

## Tips To Use
set TipsToUse {
"\0030,2 :: \00311+20 \0030Bila Anda Menjawab Dengan Benar :: \037-= GudLuck =-\037 :: \003"
}

## Proc to Randomly Select an Info Item!
proc get_InfoItem { } {
 global TipsToUse
 set outputiz2 [lindex $TipsToUse [rand [llength $TipsToUse]]]
 return $outputiz2
}

proc tmcquiz_nostops {nick host handle idx channel} {
    global rankfile uptime botnick
    global quizconf
    tmcquiz_rank_save $handle $idx {}
    tmcquiz_saveuserquests $handle $idx "all"
    tmcquiz_config_save $handle $idx {}
    mxirc_dcc $idx "$botnick now exits."
    tmcquiz_halt $handle $idx {}

}

proc tmcquiz_saves {nick host handle idx channel} {
    global rankfile uptime botnick
    global quizconf
    tmcquiz_rank_saver $handle $idx {}
    tmcquiz_saveuserquests $handle $idx "all"

}

putlog "Scramble Tcl tpi ® Succesfully LoaDeD..."
