aboutsummaryrefslogtreecommitdiff
path: root/internal/game/game.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/game.go')
-rw-r--r--internal/game/game.go215
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)
}