nakedmud-mod/
nakedmud-mod/html/tutorials/
nakedmud-mod/html/tutorials/building_extras/
nakedmud-mod/html/tutorials/c/
nakedmud-mod/html/tutorials/reference/
nakedmud-mod/html/tutorials/scripting/
nakedmud-mod/html/tutorials/scripting_extras/
nakedmud-mod/lib/
nakedmud-mod/lib/help/A/
nakedmud-mod/lib/help/B/
nakedmud-mod/lib/help/C/
nakedmud-mod/lib/help/D/
nakedmud-mod/lib/help/G/
nakedmud-mod/lib/help/H/
nakedmud-mod/lib/help/J/
nakedmud-mod/lib/help/L/
nakedmud-mod/lib/help/M/
nakedmud-mod/lib/help/O/
nakedmud-mod/lib/help/P/
nakedmud-mod/lib/help/R/
nakedmud-mod/lib/help/S/
nakedmud-mod/lib/help/W/
nakedmud-mod/lib/logs/
nakedmud-mod/lib/misc/
nakedmud-mod/lib/players/
nakedmud-mod/lib/pymodules/polc/
nakedmud-mod/lib/txt/
nakedmud-mod/lib/world/
nakedmud-mod/lib/world/zones/examples/
nakedmud-mod/lib/world/zones/examples/mproto/
nakedmud-mod/lib/world/zones/examples/oproto/
nakedmud-mod/lib/world/zones/examples/reset/
nakedmud-mod/lib/world/zones/examples/rproto/
nakedmud-mod/lib/world/zones/examples/trigger/
nakedmud-mod/lib/world/zones/limbo/
nakedmud-mod/lib/world/zones/limbo/room/
nakedmud-mod/lib/world/zones/limbo/rproto/
nakedmud-mod/src/alias/
nakedmud-mod/src/dyn_vars/
nakedmud-mod/src/editor/
nakedmud-mod/src/example_module/
nakedmud-mod/src/help2/
nakedmud-mod/src/set_val/
nakedmud-mod/src/socials/
nakedmud-mod/src/time/
'''
account_handler.py

The login and creation of accounts, and handles all account procedures, loading
and deleting, of characters.
'''
import mud, mudsys, char, hooks, account, socket, event, telnetlib, utils

# control sequences for squelching passwords
squelch   = telnetlib.IAC + telnetlib.WILL + telnetlib.ECHO
unsquelch = telnetlib.IAC + telnetlib.WONT + telnetlib.ECHO



def check_acct_name(name):
    '''Makes sure an account name is valid'''
    return (len(name) > 3 and len(name) < 13 and
            name[0].isalpha() and name.isalnum())

def login_method_prompt(sock):
    sock.send_raw("Choose an option: ")

def acct_new_password_prompt(sock):
    sock.send_raw("\r\nWhat is your new password? " + squelch)

def acct_password_prompt(sock):
    sock.send_raw("What is your password? " + squelch)

def acct_wait_dns_prompt(sock):
    sock.send_raw(" Resolving your internet address, please have patience... ")

def try_create_account(sock, name, psswd):
    if mudsys.account_exists(name):
        return False
    elif mudsys.account_creating(name):
        return False
    elif not check_acct_name(name):
        return False
    else:
        # creating a new account
        mud.log_string("Account '" + name + "' is trying to create.")

        # create our new account
        acct = mudsys.create_account(name)
        if acct == None:
            return False
        else:
            mudsys.attach_account_socket(acct, sock)
            mudsys.set_password(acct, psswd)
            sock.pop_ih()
            sock.push_ih(acct_menu_handler, acct_main_menu)

            #sock.push_ih(acct_finish_handler, acct_finish_prompt)

            # log that the account created
            mud.log_string("New account '" + acct.name + "' has created.")

            # register and save the account to disk
            mudsys.do_register(acct)

            return True
    return False

def try_load_account(sock, name, psswd):
    '''Attempt to load an account with the given name and password.'''
    if not mudsys.account_exists(name):
        return False
    else:
        acct = mudsys.load_account(name)
        if not mudsys.password_matches(acct, psswd):
            return False

        # successful load
        mudsys.attach_account_socket(acct, sock)
        sock.pop_ih()
        sock.push_ih(acct_menu_handler, acct_main_menu)
        return True
    return False

def login_method_handler(sock, arg):
    args = arg.split()
    if len(args) == 0:
        return
    args[0] = args[0].lower()

    if "create".startswith(args[0]):
        if len(args) != 3:
            return
        elif mudsys.account_exists(args[1]):
            sock.send("{cAn account by that name already exists.{n\r\n")
        elif not check_acct_name(args[1]):
            txt = "{cThat is an invalid account name. Your account name must "\
                  "only consist of characters and numbers, and it must be " \
                  "4 and 12 characters in length. The first character MUST be "\
                  "a letter. Please pick another.{n"
            sock.send(mud.format_string(txt, False))

        elif not try_create_account(sock, args[1], args[2]):
            sock.send("Your account was unable to be created.")

    elif "load".startswith(args[0]):
        if len(args) != 3:
            return
        elif not try_load_account(sock, args[1], args[2]):
            sock.send("{cInvalid account name or password.{n\r\n")
    elif "guest".startswith(args[0]):
        if (mudsys.sys_getval("lockdown") != '' and
            not utils.is_keyword(mudsys.sys_getval("lockdown"), "player")):
            sock.send("{cThe mud is currently locked out to new players.{n\r\n")
        else:
            sock.pop_ih()
            hooks.run("create_guest", hooks.build_info("sk", (sock,)))

def acct_wait_dns_handler(sock, arg):
    # do nothing
    return

def acct_new_password_handler(sock, arg):
    '''asks a new account for a password'''
    sock.send_raw(unsquelch)
    if len(arg) > 0:
        # put in mudsys to prevent scripts from messing with passwords
        mudsys.set_password(sock.account, arg)
        sock.pop_ih()

def acct_password_handler(sock, arg):
    '''asks an account to verify its password'''
    # password functions put in mudsys to prevent scripts from
    # messing with passwords
    sock.send_raw(unsquelch)
    if not mudsys.password_matches(sock.account, arg):
        sock.send("Incorrect password.")
        sock.close()
    else:
        # password matches, pop our handler and go down a level
        sock.pop_ih()

def find_reconnect(name):
    '''searches through the character list for a PC whose name matches the
       supplied name'''
    name = name.lower()
    for ch in char.char_list():
        if ch.is_pc and name == ch.name.lower():
            return ch
    return None

def acct_load_char(sock, name):
    '''loads a character attached to the account. Argument supplied must be a
       name of the corresponding character'''
    chars = sock.account.characters()
    
    if not name.lower() in [n.lower() for n in sock.account.characters()]:
        sock.send("A character by that name does not exist on your account.")
    else:
        # first, try a reconnect
        ch = find_reconnect(name)

        # no reconnect? Try loading our character
        if not ch == None:
            # swap the sockets
            old_sock = ch.sock

            # attach and detach put in mudsys to prevent scripts from
            # messing around with the connections between chars and sockets
            mudsys.detach_char_socket(ch)
            mudsys.attach_char_socket(ch, sock)
            
            if old_sock != None:
                old_sock.close()
            mud.log_string(ch.name + " has reconnected.")
            # ch.act("clear")
            ch.send("You take over a body already in use.")
            ch.act("look")
            hooks.run("reconnect", hooks.build_info("ch", (ch,)))
            sock.push_ih(mudsys.handle_cmd_input, mudsys.show_prompt, "playing")

        else:
            # load our character. Put in mudsys to prevent scripts from using it
            ch = mudsys.load_char(name)
            if ch == None:
                sock.send("The player file for " + name + " is missing.")
            elif (mudsys.sys_getval("lockdown") != '' and
                  not ch.isInGroup(mudsys.sys_getval("lockdown"))):
                sock.send("You are currently locked out of the mud.")
                mud.extract(ch)
            else:
                # attach put in mudsys to prevent scripts from messing with
                # character and socket links
                mudsys.attach_char_socket(ch, sock)
                if mudsys.try_enter_game(ch):
                    mud.log_string(ch.name + " has entered the game.")
                    sock.push_ih(mudsys.handle_cmd_input, mudsys.show_prompt,
                                 "playing")
                    # ch.act("clear")
                    ch.page(mud.get_motd())
                    ch.act("look")
                    hooks.run("enter", hooks.build_info("ch rm", (ch, ch.room)))
                else:
                    sock.send("There was a problem entering the game. " + \
                              "try again later.")
                    # detach put in mudsys t prevent scripts from messing with
                    # character and socket links
                    mudsys.detach_char_socket(ch, sock)
                    mud.extract(ch)

def acct_menu_handler(sock, arg):
    '''parses account commands (new character, enter game, quit, etc)'''
    if len(arg) == 0:
        return

    args = arg.split()
    args[0] = args[0].upper()
    
    if args[0].isdigit():
        opts = sock.account.characters()
        arg  = int(args[0])
        if arg < 0 or arg >= len(opts):
            sock.send("Invalid choice!")
        else:
            acct_load_char(sock, opts[arg])
    elif args[0] == 'L':
        if len(args) == 1:
            sock.send("Which character would you like to load?")
        else:
            acct_load_char(sock, args[1])
    elif args[0] == 'Q' or "QUIT".startswith(args[0]):
        sock.send("Come back soon!")
        mudsys.do_save(sock.account)
        sock.close()
    elif args[0] == 'P':
        # sock.push_ih(acct_confirm_password_handler,acct_confirm_password_prompt)
        sock.push_ih(acct_new_password_handler, acct_new_password_prompt)
        sock.push_ih(acct_password_handler, acct_password_prompt)
    elif args[0] == 'N':
        if "player" in [x.strip() for x in mudsys.sys_getval("lockdown").split(",")]:
            sock.send("New characters are not allowed to be created at this time.")
        else:
            hooks.run("create_character", hooks.build_info("sk", (sock,)))
    else:
        sock.send("Invalid choice!")

def display_acct_chars(sock):
    '''shows the socket a prettied list of characters tied to the account it
       has attached. Prints three names per line.'''
    num_cols   = 3
    print_room = (80 - 10*num_cols)/num_cols
    fmt        = "  {c%2d{n) %-" + str(print_room) + "s"

    sock.send("{w\r\nPlay a Character:")
    i = 0
    for ch in sock.account.characters():
        sock.send_raw(fmt % (i, ch))
        if i % num_cols == num_cols - 1:
            sock.send_raw("\r\n")
        i = i + 1

    if not i % num_cols == 0:
        sock.send_raw("\r\n")

def acct_main_menu(sock):
    '''displays the main menu for the account and asks for a command'''
    line_buf = "%-38s" % " "

    # make the account menu look pretty
    img = ["+--------------------------------------+",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|               I M A G E              |",
           "|                H E R E               |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "|                                      |",
           "+--------------------------------------+"]

    # the lines for displaying our account options
    opts  = [ ]
    chars = sock.account.characters()
    chars.sort()
    if len(chars) > 0:
        opts.append("  {wPlay A Character:                   ")
        for i in range(len(chars)):
            opts.append("    {n[{c%1d{n] %-30s" % (i,chars[i]))
        opts.append(line_buf)

    # append our other displays
    opts.append("  {wAccount Management:                 ")
    opts.append("    {n[{cP{n]assword change                 ")
    opts.append("    {n[{cN{n]ew character                   ")
    opts.append("    {n[{cL{n]oad character <name>           ")
    opts.append(line_buf)
    opts.append(line_buf)
    opts.append(line_buf)

    # fill up our height to be in line with the image
    while len(opts) < len(img) - 2:
        opts.insert(0, line_buf)

    # append our title
    opts.insert(1, "            {nW E L C O M E  T O        ")
    opts.insert(2, "             {nN A K E D M U D          ")
            
    # display all of our info
    sock.send("")
    for i in range(max(len(opts), len(img))):
        if i < len(opts):
            sock.send_raw(opts[i])
        else:
            sock.send_raw(line_buf)
        if i < len(img):
            sock.send_raw("{n%s" % img[i])
        sock.send("")
        
    sock.send_raw("{nEnter choice, or Q to quit: ")



################################################################################
# events for blocking action when dns lookup is in progress
################################################################################
def dns_check_event(owner, void, info):
    '''After a socket connects, monitor their hostname until dns lookup is
       complete. Then, put the socket into the account handler.
    '''
    sock, = hooks.parse_info(info)
    if sock != None and sock.can_use:
        sock.send(" Lookup complete.")
        sock.send("================================================================================")
        sock.send(mud.get_greeting())
        sock.pop_ih()
        sock.bust_prompt()
        # mud.log_string("new connection from " + sock.hostname)
    else:
        event.start_event(None, 0.2, dns_check_event, None, info)



################################################################################
# account handler hooks
################################################################################
def account_handler_hook(info):
    '''When a socket connects, put them into the account handler menu.'''
    # put a nonfunctional prompt up while waiting for the DNS to resolve
    sock, = hooks.parse_info(info)
    sock.push_ih(login_method_handler, login_method_prompt)
    mud.log_string("new socket, %d, attempting to connect" % sock.uid)
    sock.send(mud.get_greeting())
    sock.send("== Options Are ================================================================")
    sock.send("    Load account   : load   <account> <password>")
    sock.send("    Create account : create <account> <password>")
    sock.send("    Play as guest  : guest")
    sock.send("===============================================================================")
    sock.send("")

    '''
    sock.push_ih(acct_wait_dns_handler, acct_wait_dns_prompt)
    sock.send("================================================================================")
    event.start_event(None, 0.2, dns_check_event, None, info)
    '''

def copyover_complete_hook(info):
    sock, = hooks.parse_info(info)
    sock.push_ih(acct_menu_handler, acct_main_menu)
    sock.push_ih(mudsys.handle_cmd_input, mudsys.show_prompt, "playing")



################################################################################
# loading and unloading the module
################################################################################
hooks.add("receive_connection", account_handler_hook)
hooks.add("copyover_complete",  copyover_complete_hook)

def __unload__():
    '''removes the hooks for account handling'''
    hooks.remove("receive_connection", account_handler_hook)
    hooks.remove("copyover_complete",  copyover_complete_hook)