package game import ( "fmt" "os" "strings" "thirdcollapse/internal/net" "thirdcollapse/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 = &net.AccountEntry{Name: actualName} sess.State = net.StatePassword sess.Write("Password: ") } else { sess.Account = &net.AccountEntry{Name: name} sess.State = net.StateNewAccountPass sess.Write("A new visitor to this rock!\nChoose password: ") } } func (g *Game) handlePassword(sess *net.Session, input string) { if input == "" { sess.Write("Password: ") return } acc, err := g.AccountStore.LoadAccount(sess.Account.Name) if err != nil { sess.WriteLine("Error loading account.") sess.State = net.StateAccountName sess.Write("Account name: ") return } if !player.CheckPassword(input, acc.PasswordHash) { sess.WriteLine("Wrong password.") sess.State = net.StateAccountName sess.Write("Account name: ") return } sess.Account = &net.AccountEntry{ Name: acc.Name, PasswordHash: acc.PasswordHash, Characters: acc.Characters, } g.showMenu(sess) } func (g *Game) handleNewAccountPass(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } sess.PendingPass = input sess.State = net.StateNewAccountConfirm sess.Write("Confirm password: ") } func (g *Game) handleNewAccountConfirm(sess *net.Session, input string) { input = strings.TrimSpace(input) if input == "" { sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } if input != sess.PendingPass { 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.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.WriteLine(fmt.Sprintf("Error creating account: %v", err)) sess.Account = nil sess.State = net.StateAccountName sess.Write("Account name: ") return } sess.Account = &net.AccountEntry{ Name: acc.Name, PasswordHash: acc.PasswordHash, } sess.PendingPass = "" g.showMenu(sess) } func (g *Game) showMenu(sess *net.Session) { sess.State = net.StateMenu lines := []string{ "", fmt.Sprintf("SUCCESSFUL LOGIN as %s.", sess.Account.Name), "", } if len(sess.Account.Characters) > 0 { lines = append(lines, " (C)onnect character to TC", "", " (L)ist characters", " (R)ename character", " (D)elete character", ) } lines = append(lines, " (N)ew character", " (P)urge account", " (A)ccount rename", " (Q)uit", "", "INPUT: ", ) sess.WriteLines(lines...) } func (g *Game) handleMenu(sess *net.Session, input string) { switch strings.ToLower(strings.TrimSpace(input)) { case "": sess.Write("INPUT: ") case "c": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nSYSTEM ERROR: Make a character with 'N' first!") sess.Write("\nINPUT: ") 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("\nChoice: ") case "l": if len(sess.Account.Characters) == 0 { sess.WriteLine("\nSYSTEM ERROR: Zero characters on this account.\n(Make a character with 'N' first)") } else { sess.WriteLine("\nSELECT Humans FROM GaiaZeroFour:") for _, name := range sess.Account.Characters { sess.WriteLine(fmt.Sprintf(" - %s", name)) } } sess.Write("\nINPUT: ") 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("\nNo characters to rename.") sess.Write("\nChoice: ") 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("\nINPUT: ") 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.Conn.Close() return default: sess.Write("SYNTAX ERROR\nINPUT: ") } } func (g *Game) handleNewCharName(sess *net.Session, input string) { name := strings.TrimSpace(input) if sess.PendingChar == "connect" && len(sess.Account.Characters) > 0 { if idx, err := parseIndex(name); err == nil && idx > 0 && idx <= len(sess.Account.Characters) { sess.PendingChar = "" g.connectCharacter(sess, sess.Account.Characters[idx-1]) return } sess.WriteLine("Invalid choice.") sess.Write("\nChoice: ") return } if name == "" { sess.Write("Character name: ") return } if g.AccountStore.CharacterExists(name) { sess.WriteLine("A character with that name already exists.") sess.Write("Character name: ") return } p := player.New(name) p.RoomID = 1 if err := g.AccountStore.SaveCharacter(p); err != nil { sess.WriteLine(fmt.Sprintf("Error creating character: %v", err)) sess.State = net.StateMenu g.showMenu(sess) return } acc, _ := g.AccountStore.LoadAccount(sess.Account.Name) acc.Characters = append(acc.Characters, name) g.AccountStore.SaveAccount(acc) sess.Account.Characters = acc.Characters g.connectCharacter(sess, name) } func (g *Game) connectCharacter(sess *net.Session, name string) { g.charsMu.Lock() if existing := g.loggedInChars[name]; existing != nil { g.charsMu.Unlock() sess.WriteLine("This character is logged in elsewhere.") sess.State = net.StateMenu g.showMenu(sess) return } p, err := g.AccountStore.LoadCharacter(name) if err != nil { g.charsMu.Unlock() sess.WriteLine(fmt.Sprintf("Error loading character: %v", err)) sess.State = net.StateMenu g.showMenu(sess) return } sess.Player = p sess.State = net.StateGame g.loggedInChars[name] = sess g.charsMu.Unlock() g.World.SeedGroundItems(p.RoomID) g.seedRoomMobs(p.RoomID) g.ensureRoomObjects(p.RoomID) if g.Hub != nil { g.Hub.EnterRoom(sess, p.RoomID) } g.doLook(sess) sess.Write("\r\n> ") } func (g *Game) handleRenameAccount(sess *net.Session, input string) { newName := strings.TrimSpace(input) if newName == "" { 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) handleRenameChar(sess *net.Session, input string) { name := strings.TrimSpace(input) if idx, err := parseIndex(name); err == nil && idx > 0 && idx <= len(sess.Account.Characters) { sess.PendingChar = sess.Account.Characters[idx-1] } else { sess.PendingChar = name } found := false for _, c := range sess.Account.Characters { if c == sess.PendingChar { found = true break } } if !found { sess.WriteLine(fmt.Sprintf("No character named %s on this account.", sess.PendingChar)) g.showMenu(sess) return } sess.State = net.StateRenameCharName sess.WriteLine(fmt.Sprintf("Renaming %s.", sess.PendingChar)) sess.Write("New name: ") } func (g *Game) handleRenameCharName(sess *net.Session, input string) { newName := strings.TrimSpace(input) oldName := sess.PendingChar if newName == "" { sess.Write("New name: ") return } if newName == oldName { sess.WriteLine("That's already the character's name.") g.showMenu(sess) return } if g.AccountStore.CharacterExists(newName) { sess.WriteLine("A character with that name already exists.") sess.Write("New name: ") return } oldPath := g.AccountStore.CharPath(oldName) newPath := g.AccountStore.CharPath(newName) if err := os.Rename(oldPath, newPath); err != nil { sess.WriteLine(fmt.Sprintf("Error renaming: %v", err)) g.showMenu(sess) return } p, _ := g.AccountStore.LoadCharacter(newName) if p != nil { p.Name = newName g.AccountStore.SaveCharacter(p) } acc, _ := g.AccountStore.LoadAccount(sess.Account.Name) for i, c := range acc.Characters { if c == oldName { acc.Characters[i] = newName break } } g.AccountStore.SaveAccount(acc) sess.Account.Characters = acc.Characters sess.PendingChar = "" sess.WriteLine(fmt.Sprintf("%s renamed to %s.", oldName, newName)) g.showMenu(sess) } func (g *Game) showDeleteConfirm(sess *net.Session) { sess.State = net.StateDeleteChar sess.WriteLine(fmt.Sprintf("\nDelete %s? Type DELETE %s to confirm, or anything else to cancel.", sess.PendingChar, sess.PendingChar)) } func (g *Game) handleDeleteChar(sess *net.Session, input string) { if sess.PendingChar == "" { name := strings.TrimSpace(input) if idx, err := parseIndex(name); err == nil && idx > 0 && idx <= len(sess.Account.Characters) { sess.PendingChar = sess.Account.Characters[idx-1] } else { sess.PendingChar = name } found := false for _, c := range sess.Account.Characters { if c == sess.PendingChar { found = true break } } if !found { sess.WriteLine(fmt.Sprintf("No character named %s on this account.", sess.PendingChar)) sess.PendingChar = "" g.showMenu(sess) return } g.showDeleteConfirm(sess) return } input = strings.TrimSpace(input) expected := "DELETE " + sess.PendingChar if strings.ToUpper(input) != strings.ToUpper(expected) { sess.WriteLine("Delete cancelled.") sess.PendingChar = "" g.showMenu(sess) return } charPath := g.AccountStore.CharPath(sess.PendingChar) if err := os.Remove(charPath); err != nil { sess.WriteLine(fmt.Sprintf("Error deleting: %v", err)) sess.PendingChar = "" g.showMenu(sess) return } acc, _ := g.AccountStore.LoadAccount(sess.Account.Name) var newChars []string for _, c := range acc.Characters { if c != sess.PendingChar { newChars = append(newChars, c) } } acc.Characters = newChars g.AccountStore.SaveAccount(acc) sess.Account.Characters = newChars sess.WriteLine(fmt.Sprintf("%s has been deleted.", sess.PendingChar)) sess.PendingChar = "" 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.Conn.Close() }