aboutsummaryrefslogtreecommitdiff
path: root/internal/game/session.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-10 01:48:28 -0400
committerhistoria <[not public]>2026-06-10 01:48:28 -0400
commita226d72e51eecb768b13600303f73483118d9104 (patch)
tree458d15ef049732c15dacc591a830d3beddbedfde /internal/game/session.go
parent6a0f3d7a252de4b1741cfe5e1c561412c602becc (diff)
downloadthehouseoficarus-a226d72e51eecb768b13600303f73483118d9104.tar.gz
feat: implemented janky object interaction model
Diffstat (limited to 'internal/game/session.go')
-rw-r--r--internal/game/session.go500
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()
+}