package game import ( "fmt" "os" "strings" "thehouseoficarus/internal/color" "thehouseoficarus/internal/net" "thehouseoficarus/internal/player" ) func (g *Game) handleAccountName(sess *net.Session, input string) { name := strings.TrimSpace(input) if name == "" { sess.Write("Account name: ") return } actualName, err := g.AccountStore.FindAccount(name) if err == nil { sess.Account = &player.Account{Name: actualName} sess.State = net.StatePassword sess.Conn.SetEcho(false) sess.Write("Password: ") } else { if verr := player.ValidName(name); verr != nil { sess.WriteLine(verr.Error()) sess.Write("Account name: ") return } sess.Account = &player.Account{Name: name} sess.State = net.StateNewAccountPass sess.Conn.SetEcho(false) sess.WriteLine("\r\nOh, a new visitor to this rock!") sess.Write("Choose password: ") } } func (g *Game) handlePassword(sess *net.Session, input string) { if input == "" { sess.Write("Password: ") return } if len(input) > 128 { sess.Conn.SetEcho(true) sess.WriteLine("Wrong password.") sess.State = net.StateAccountName sess.Write("Account name: ") return } acc, err := g.AccountStore.LoadAccount(sess.Account.Name) if err != nil { sess.Conn.SetEcho(true) sess.WriteLine("Error loading account.") sess.State = net.StateAccountName sess.Write("Account name: ") return } if !player.CheckPassword(input, acc.PasswordHash) { sess.Conn.SetEcho(true) sess.WriteLine("Wrong password.") sess.State = net.StateAccountName sess.Write("Account name: ") return } sess.Conn.SetEcho(true) sess.Account = &player.Account{ Name: acc.Name, PasswordHash: acc.PasswordHash, Characters: acc.Characters, Aliases: acc.Aliases, Colors: acc.Colors, Options: acc.Options, Admin: acc.Admin, } if sess.Account.Aliases == nil { sess.Account.Aliases = make(map[string]string) } if sess.Account.Colors == nil { sess.Account.Colors = make(map[string]string) } if sess.Account.Options == nil { sess.Account.Options = make(map[string]any) } g.showMenu(sess) } func (g *Game) handleNewPass(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Conn.SetEcho(true) sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } if verr := player.ValidPassword(input); verr != nil { sess.WriteLine(verr.Error()) sess.Write("Choose password: ") return } sess.PendingPass = input sess.State = net.StateNewAccount sess.Write("Confirm password: ") } func (g *Game) handleNewAccount(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Conn.SetEcho(true) sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } if input != sess.PendingPass { sess.Conn.SetEcho(true) sess.WriteLine("Passwords do not match.") sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } hash, err := player.HashPassword(input) if err != nil { sess.Conn.SetEcho(true) sess.WriteLine(fmt.Sprintf("Error creating account: %v", err)) sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } acc := &player.Account{ Name: sess.Account.Name, PasswordHash: hash, } if err := g.AccountStore.SaveAccount(acc); err != nil { sess.Conn.SetEcho(true) sess.WriteLine(fmt.Sprintf("Error creating account: %v", err)) sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } sess.Conn.SetEcho(true) sess.Account = &player.Account{ Name: acc.Name, PasswordHash: acc.PasswordHash, Aliases: make(map[string]string), Colors: make(map[string]string), Options: make(map[string]any), } sess.PendingPass = "" sess.State = net.StateColorChoice sess.Write("\nShould I disable color? [y/N] ") } func (g *Game) handleColorChoice(sess *net.Session, input string) { input = strings.ToLower(strings.TrimSpace(input)) if input == "y" || input == "yes" { sess.Account.Options["color"] = "none" acc, err := g.AccountStore.LoadAccount(sess.Account.Name) if err == nil { if acc.Options == nil { acc.Options = make(map[string]any) } acc.Options["color"] = "none" g.AccountStore.SaveAccount(acc) } } g.showMenu(sess) } func menuColorMode(sess *net.Session) string { if sess != nil && sess.Account != nil && sess.Account.Options != nil { if v, ok := sess.Account.Options["color"]; ok { if s, ok := v.(string); ok && s != "" { return s } } } return "xterm256" } func (g *Game) showMenu(sess *net.Session) { sess.State = net.StateMenu mode := menuColorMode(sess) lines := []string{ "", fmt.Sprintf("Welcome back to Vesta, %s.", sess.Account.Name), "", } if len(sess.Account.Characters) > 0 { lines = append(lines, color.ExpandTags(mode, " {03}(C){/}{04}onnect character to THOI{/}"), "", color.ExpandTags(mode, " {03}(L){/}{04}ist characters{/}"), color.ExpandTags(mode, " {03}(R){/}{04}ename character{/}"), color.ExpandTags(mode, " {03}(D){/}{04}elete character{/}"), ) } lines = append(lines, color.ExpandTags(mode, " {03}(N){/}{04}ew character{/}"), color.ExpandTags(mode, " {03}(P){/}{04}urge account{/}"), color.ExpandTags(mode, " {03}(A){/}{04}ccount rename{/}"), color.ExpandTags(mode, " {03}(Q){/}{04}uit{/}"), "", ) sess.WriteLines(lines...) sess.Write("> ") } func (g *Game) handleMenu(sess *net.Session, input string) { switch strings.ToLower(strings.TrimSpace(input)) { case "": sess.Write("> ") case "c": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nHey, make a character with 'N' first!") sess.Write("\n> ") return } if len(sess.Account.Characters) == 1 { g.connectCharacter(sess, sess.Account.Characters[0]) return } sess.WriteLine("\nSelect character:") for i, name := range sess.Account.Characters { sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, name)) } sess.State = net.StateNewCharName sess.PendingChar = "connect" sess.Write("\n> ") case "l": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nYou don't have any characters! Make one with 'N'!") } else { sess.WriteLine("\nCharacters:") for _, name := range sess.Account.Characters { sess.WriteLine(fmt.Sprintf(" - %s", name)) } } sess.Write("\n> ") case "n": sess.State = net.StateNewCharName sess.PendingChar = "" sess.Write("What's your character name?: ") case "a": sess.State = net.StateRenameAccount sess.Write("New account name: ") case "r": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nRename who? You don't have any characters!") sess.Write("\n> ") return } if len(sess.Account.Characters) == 1 { sess.PendingChar = sess.Account.Characters[0] sess.State = net.StateRenameCharName sess.WriteLine(fmt.Sprintf("\nRenaming %s.", sess.PendingChar)) sess.Write("New name: ") return } sess.State = net.StateRenameChar sess.WriteLine("\nRename which character?") for i, name := range sess.Account.Characters { sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, name)) } sess.Write("\n> ") case "d": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nNo characters to delete.") sess.Write("\n> ") return } if len(sess.Account.Characters) == 1 { sess.PendingChar = sess.Account.Characters[0] } else { sess.State = net.StateDeleteChar sess.WriteLine("\nDelete which character?") for i, name := range sess.Account.Characters { sess.WriteLine(fmt.Sprintf(" %d) %s", i+1, name)) } sess.Write("\n> ") return } g.showDeleteConfirm(sess) case "p": sess.State = net.StatePurgeAccount sess.WriteLine(fmt.Sprintf("\nPurge account %s and all characters?\nTo be clear you are about to PERMANENTLY DELETE EVERYTHING!\nType PURGE %s to confirm, or anything else to cancel.", sess.Account.Name, sess.Account.Name)) sess.Write("\n> ") case "q": sess.WriteLine("Later.") sess.Close() return default: sess.Write("> ") } } func (g *Game) handleRenameAccount(sess *net.Session, input string) { newName := strings.TrimSpace(input) if newName == "" { sess.Write("New account name: ") return } if verr := player.ValidName(newName); verr != nil { sess.WriteLine(verr.Error()) sess.Write("New account name: ") return } oldName := sess.Account.Name if newName == oldName { sess.WriteLine("That's already your account name.") g.showMenu(sess) return } if g.AccountStore.AccountExists(newName) { sess.WriteLine("An account with that name already exists.") sess.Write("New account name: ") return } oldPath := g.AccountStore.AccountPath(oldName) newPath := g.AccountStore.AccountPath(newName) if err := os.Rename(oldPath, newPath); err != nil { sess.WriteLine(fmt.Sprintf("Error renaming account: %v", err)) g.showMenu(sess) return } sess.Account.Name = newName sess.WriteLine(fmt.Sprintf("Account renamed to %s.", newName)) g.showMenu(sess) } func (g *Game) handlePurgeAccount(sess *net.Session, input string) { input = strings.TrimSpace(input) expected := "PURGE " + sess.Account.Name if strings.ToUpper(input) != strings.ToUpper(expected) { sess.WriteLine("Purge cancelled.") g.showMenu(sess) return } for _, name := range sess.Account.Characters { os.Remove(g.AccountStore.CharPath(name)) } os.Remove(g.AccountStore.AccountPath(sess.Account.Name)) sess.WriteLine(fmt.Sprintf("Account %s has been purged. Thanks for playing.", sess.Account.Name)) sess.Close() }