diff options
| author | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-06-10 01:48:28 -0400 |
| commit | a226d72e51eecb768b13600303f73483118d9104 (patch) | |
| tree | 458d15ef049732c15dacc591a830d3beddbedfde /internal/game/session.go | |
| parent | 6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff) | |
| download | thehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz | |
feat: implemented janky object interaction model
Diffstat (limited to 'internal/game/session.go')
| -rw-r--r-- | internal/game/session.go | 500 |
1 files changed, 500 insertions, 0 deletions
diff --git a/internal/game/session.go b/internal/game/session.go new file mode 100644 index 0000000..edc904d --- /dev/null +++ b/internal/game/session.go @@ -0,0 +1,500 @@ +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() +} |
