diff options
Diffstat (limited to 'internal/game/game.go')
| -rw-r--r-- | internal/game/game.go | 215 |
1 files changed, 192 insertions, 23 deletions
diff --git a/internal/game/game.go b/internal/game/game.go index cecde2a..37a1f3f 100644 --- a/internal/game/game.go +++ b/internal/game/game.go @@ -2,8 +2,10 @@ package game import ( "fmt" + "sort" "strings" "sync" + "time" "thirdcollapse/internal/action" "thirdcollapse/internal/engine" @@ -13,6 +15,21 @@ import ( "thirdcollapse/internal/world" ) +type CommandClass int + +const ( + ClassInstant CommandClass = iota + ClassFree + ClassActive +) + +type QueuedCommand struct { + Session *net.Session + Command string + Args string + Timestamp time.Time +} + type Game struct { World *world.World ObjectStore *object.ObjectStore @@ -28,22 +45,28 @@ type Game struct { charsMu sync.Mutex loggedInChars map[string]*net.Session combatPadWidth int - MapWidth int + GameSpeed float64 + freeQueue map[string][]QueuedCommand + activeQueue map[string]*QueuedCommand + pendingDepletions []pendingDepletion } 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), + 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), + freeQueue: make(map[string][]QueuedCommand), + activeQueue: make(map[string]*QueuedCommand), + pendingDepletions: nil, } } @@ -94,17 +117,26 @@ func (g *Game) HandleSession(sess *net.Session, input string) { } } +func classifyCommand(cmd string) CommandClass { + switch cmd { + case "say", "score", "sc", "inventory", "i", "inv", + "equipment", "eq", "look", "l", "exits", "help", + "map", "option", "options", "alias", "unalias", + "description", "desc", "queued": + return ClassInstant + case "wear", "wield", "remove", "unwear", "unwield", "style": + return ClassFree + default: + return ClassActive + } +} + func (g *Game) handleGameCommand(sess *net.Session, input string) { if input == "" { sess.Write("> ") return } - p, _ := sess.Player.(*player.Player) - if p != nil { - g.cancelRest(p.Name) - } - if sess.Account != nil && sess.Account.Aliases != nil { firstSpace := strings.Index(input, " ") var firstWord, rest string @@ -125,7 +157,48 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { parts := strings.Fields(strings.ToLower(input)) cmd := parts[0] - args := parts[1:] + class := classifyCommand(cmd) + + if class == ClassInstant { + g.executeCommand(sess, cmd, parts[1:], input) + sess.Write("\r\n> ") + return + } + + p, _ := sess.Player.(*player.Player) + if p == nil { + return + } + + if class == ClassFree { + g.freeQueue[p.Name] = append(g.freeQueue[p.Name], QueuedCommand{ + Session: sess, + Command: cmd, + Args: strings.Join(parts[1:], " "), + Timestamp: time.Now(), + }) + if !p.OptionBool("queue_actions_silently") { + sess.WriteLine(fmt.Sprintf("\nYou prepare to %s.", cmd)) + } + sess.Write("\r\n> ") + return + } + + g.activeQueue[p.Name] = &QueuedCommand{ + Session: sess, + Command: cmd, + Args: strings.Join(parts[1:], " "), + Timestamp: time.Now(), + } + g.cancelRest(p.Name) + if !p.OptionBool("queue_actions_silently") { + sess.WriteLine(fmt.Sprintf("\nYou prepare to %s.", cmd)) + } + sess.Write("\r\n> ") +} + +func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawInput string) { + p, _ := sess.Player.(*player.Player) switch cmd { case "get", "take", "grab", "pick": @@ -153,6 +226,9 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { g.doDrop(sess, strings.Join(args, " ")) } case "attack", "kill": + if p == nil { + return + } if len(args) == 0 { target := g.resolveDefaultMob(p.RoomID) if target == "" { @@ -183,9 +259,9 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { 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:]) + msgStart := strings.Index(strings.ToLower(rawInput), "say ") + 4 + if msgStart >= 4 && msgStart < len(rawInput) { + g.doSay(sess, rawInput[msgStart:]) } else { g.doSay(sess, strings.Join(args, " ")) } @@ -207,7 +283,8 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { g.doExits(sess) case "map": g.doMap(sess) - + case "queued": + g.doQueued(sess) case "help": if len(args) == 0 { g.doHelp(sess, "") @@ -215,6 +292,10 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { g.doHelp(sess, strings.Join(args, " ")) } case "mine", "chop", "fish", "cut", "use", "pull", "push": + if p == nil { + return + } + g.CancelAction(p) if len(args) == 0 { target := g.resolveDefaultTarget(p.RoomID, cmd) if target == "" { @@ -228,6 +309,10 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { return } case "talk", "speak", "ask": + if p == nil { + return + } + g.CancelAction(p) if len(args) == 0 { sess.WriteLine("Talk to whom?") } else { @@ -235,9 +320,17 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { return } case "burn": + if p == nil { + return + } + g.CancelAction(p) g.doBurn(sess, p, strings.Join(args, " ")) return case "stoke": + if p == nil { + return + } + g.CancelAction(p) g.doStoke(sess, p, strings.Join(args, " ")) return case "alias": @@ -247,6 +340,10 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { g.doUnalias(sess, args) return case "search": + if p == nil { + return + } + g.CancelAction(p) if len(args) == 0 { sess.WriteLine("Search what?") } else { @@ -254,14 +351,86 @@ func (g *Game) handleGameCommand(sess *net.Session, input string) { } return case "wear", "wield": + if p == nil { + return + } g.doWear(sess, strings.Join(args, " ")) return case "remove", "unwear", "unwield": + if p == nil { + return + } g.doRemove(sess, strings.Join(args, " ")) return default: sess.WriteLine("Unknown command.") } +} - sess.Write("\r\n> ") +func (g *Game) ProcessQueuedCommands() { + if g.Hub == nil { + return + } + + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil || p.ActionState == nil { + continue + } + as, ok := p.ActionState.(*ActionState) + if !ok { + continue + } + switch as.Type { + case ActionGathering, ActionCombating, ActionUsing, ActionTalking, + ActionToggling, ActionBurning, ActionStoking, ActionResting: + default: + p.ActionState = nil + } + } + + for _, sess := range g.Hub.AllSessions() { + p, ok := sess.Player.(*player.Player) + if !ok || p == nil { + continue + } + cmds := g.freeQueue[p.Name] + for _, qc := range cmds { + g.cancelRest(p.Name) + parts := strings.Fields(qc.Command + " " + qc.Args) + if len(parts) > 0 { + g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args) + } + } + delete(g.freeQueue, p.Name) + } + + var actives []QueuedCommand + for _, qc := range g.activeQueue { + actives = append(actives, *qc) + } + sort.Slice(actives, func(i, j int) bool { + return actives[i].Timestamp.Before(actives[j].Timestamp) + }) + + for _, qc := range actives { + parts := strings.Fields(qc.Command + " " + qc.Args) + if len(parts) > 0 { + g.executeCommand(qc.Session, parts[0], parts[1:], qc.Command+" "+qc.Args) + } + } + g.activeQueue = make(map[string]*QueuedCommand) + + g.flushPendingDepletions() +} + +type pendingDepletion struct { + instanceKey string + behaviorID string + targetName string + playerNames []string +} + +func (g *Game) computeTicks(base float64) int { + return engine.FractionalTicks(base, g.GameSpeed) } |
