package game import ( "fmt" "strings" "sync" "thirdcollapse/internal/action" "thirdcollapse/internal/engine" "thirdcollapse/internal/net" "thirdcollapse/internal/object" "thirdcollapse/internal/player" "thirdcollapse/internal/world" ) type Game struct { World *world.World ObjectStore *object.ObjectStore ItemStore *object.ItemStore AccountStore *player.AccountStore MobStore *world.MobStore BehaviorStore *action.Store Hub *net.Hub Ticks *engine.Engine WorldFlags map[string]any dataDir string restTimers map[string]uint64 charsMu sync.Mutex loggedInChars map[string]*net.Session combatPadWidth int } func New(dataDir string) *Game { return &Game{ World: world.New(dataDir), ObjectStore: object.NewObjectStore(dataDir), ItemStore: object.NewItemStore(dataDir), AccountStore: player.NewAccountStore(dataDir), MobStore: world.NewMobStore(dataDir), BehaviorStore: action.NewStore(dataDir), Ticks: engine.New(), WorldFlags: make(map[string]any), dataDir: dataDir, restTimers: make(map[string]uint64), loggedInChars: make(map[string]*net.Session), } } func (g *Game) SetHub(hub *net.Hub) { g.Hub = hub hub.OnRemove(func(sess *net.Session) { if p, ok := sess.Player.(*player.Player); ok { g.charsMu.Lock() delete(g.loggedInChars, p.Name) g.charsMu.Unlock() } }) } func (g *Game) HandleSession(sess *net.Session, input string) { input = player.StripControl(input) switch sess.State { case net.StateAccountName: g.handleAccountName(sess, input) case net.StatePassword: g.handlePassword(sess, input) case net.StateNewAccountPass: g.handleNewAccountPass(sess, input) case net.StateNewAccountConfirm: g.handleNewAccountConfirm(sess, input) case net.StateMenu: g.handleMenu(sess, input) case net.StateNewCharName: g.handleNewCharName(sess, input) case net.StateRenameAccount: g.handleRenameAccount(sess, input) case net.StateRenameChar: g.handleRenameChar(sess, input) case net.StateRenameCharName: g.handleRenameCharName(sess, input) case net.StateDeleteChar: g.handleDeleteChar(sess, input) case net.StatePurgeAccount: g.handlePurgeAccount(sess, input) case net.StateGame: g.handleGameCommand(sess, input) case net.StateChangeDescription: g.handleDescriptionChange(sess, input) case net.StateTalk: g.handleTalkInput(sess, input) case net.StateDropAllConfirm: g.handleDropAllConfirm(sess, input) } } func (g *Game) handleGameCommand(sess *net.Session, input string) { if input == "" { sess.Write("> ") return } if p, ok := sess.Player.(*player.Player); ok { g.cancelRest(p.Name) } if sess.Account != nil && sess.Account.Aliases != nil { firstSpace := strings.Index(input, " ") var firstWord, rest string if firstSpace > 0 { firstWord = input[:firstSpace] rest = strings.TrimLeft(input[firstSpace:], " ") } else { firstWord = input } if expansion, ok := sess.Account.Aliases[strings.ToLower(firstWord)]; ok { if rest != "" { input = expansion + " " + rest } else { input = expansion } } } parts := strings.Fields(strings.ToLower(input)) cmd := parts[0] args := parts[1:] switch cmd { case "get", "take", "grab", "pick": if len(args) == 0 { sess.WriteLine("Get what?") } else if args[0] == "all" { if len(args) == 1 { g.doGetAll(sess) } else { g.doGetAllNamed(sess, strings.Join(args[1:], " ")) } } else { g.doGet(sess, strings.Join(args, " ")) } case "drop": if len(args) == 0 { sess.WriteLine("Drop what?") } else if args[0] == "all" { if len(args) == 1 { g.doDropAll(sess) } else { g.doDropAllNamed(sess, strings.Join(args[1:], " ")) } } else { g.doDrop(sess, strings.Join(args, " ")) } case "attack", "kill": if len(args) == 0 { sess.WriteLine("Attack what?") } else { g.doAttack(sess, strings.Join(args, " ")) return } case "style": if len(args) == 0 { g.doStyle(sess, "") } else { g.doStyle(sess, args[0]) } case "look", "l": if len(args) == 0 { g.doLook(sess) } else { g.doLookTarget(sess, strings.Join(args, " ")) } case "north", "n", "south", "s", "east", "e", "west", "w", "up", "u", "down", "d": g.doMove(sess, cmd) case "say": if len(args) == 0 { sess.WriteLine("Say what?") } else { msgStart := strings.Index(strings.ToLower(input), "say ") + 4 if msgStart >= 4 && msgStart < len(input) { g.doSay(sess, input[msgStart:]) } else { g.doSay(sess, strings.Join(args, " ")) } } case "sc", "score": g.doScore(sess) case "i", "inv", "inventory": g.doInventory(sess) case "eq", "equipment": g.doEquipment(sess) case "quit": g.doQuit(sess) return case "description", "desc": g.doDescription(sess) case "toggle": g.doToggle(sess, strings.Join(args, " ")) case "exits": g.doExits(sess) case "help": if len(args) == 0 { g.doHelp(sess, "") } else { g.doHelp(sess, strings.Join(args, " ")) } case "mine", "chop", "fish", "cut", "use", "pull", "push": if len(args) == 0 { sess.WriteLine(fmt.Sprintf("%s what?", cmd)) } else { g.StartAction(sess, cmd, strings.Join(args, " ")) return } case "talk", "speak", "ask": if len(args) == 0 { sess.WriteLine("Talk to whom?") } else { g.StartAction(sess, "talk", strings.Join(args, " ")) return } case "alias": g.doAlias(sess, args) return case "unalias": g.doUnalias(sess, args) return case "search": if len(args) == 0 { sess.WriteLine("Search what?") } else { g.doSearch(sess, strings.Join(args, " ")) } return case "wear", "wield": g.doWear(sess, strings.Join(args, " ")) return case "remove", "unwear", "unwield": g.doRemove(sess, strings.Join(args, " ")) return default: sess.WriteLine("Unknown command.") } sess.Write("\r\n> ") }