aboutsummaryrefslogtreecommitdiff
path: root/internal/game/action_gather.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-06-14 22:38:23 -0400
committerhistoria <[not public]>2026-06-14 22:38:23 -0400
commita6f0ad79e2e3a5b7c11eef1ffc3233f3a2766f77 (patch)
tree89601a502bbfcb50302cf03f09b6febc6040eeb0 /internal/game/action_gather.go
parent1aba2bdd23aebed4032333e74aa553c40a31fcbd (diff)
downloadthehouseoficarus-a6f0ad79e2e3a5b7c11eef1ffc3233f3a2766f77.tar.gz
feat: fractional ticks and server game_speed implemented. potentially insanely janky.
Diffstat (limited to 'internal/game/action_gather.go')
-rw-r--r--internal/game/action_gather.go140
1 files changed, 107 insertions, 33 deletions
diff --git a/internal/game/action_gather.go b/internal/game/action_gather.go
index 201a619..512be81 100644
--- a/internal/game/action_gather.go
+++ b/internal/game/action_gather.go
@@ -29,13 +29,13 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
if cfg.DepletedMessage != "" {
msg := cfg.DepletedMessage
if p.OptionBool("depletion") {
- msg += fmt.Sprintf(" (%d ticks to respawn)", st.DepleteTimer)
+ msg += fmt.Sprintf(" (%d ticks to respawn)", int(st.DepleteTimer))
}
sess.WriteLine(msg)
} else if cfg.DepleteTimer > 0 {
- sess.WriteLine(fmt.Sprintf("The %s has been cut down for %d more ticks.", obj.Name, st.DepleteTimer))
+ sess.WriteLine(fmt.Sprintf("The %s has been cut down for %d more ticks.", obj.Name, int(st.DepleteTimer)))
} else {
- sess.WriteLine(fmt.Sprintf("The %s is depleted for %d more ticks.", obj.Name, st.DepleteTimer))
+ sess.WriteLine(fmt.Sprintf("The %s is depleted for %d more ticks.", obj.Name, int(st.DepleteTimer)))
}
return
}
@@ -43,7 +43,7 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
wait := cfg.BaseWait
if len(cfg.Tools) > 0 {
- toolSpeed := -1
+ var toolSpeed float64 = -1
if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
def, err := g.ItemStore.Load(itemID)
if err == nil {
@@ -107,6 +107,39 @@ func (g *Game) startGather(sess *net.Session, p *player.Player, obj *object.Obje
return
}
+ toolName := ""
+ if itemID, ok := p.Equipment[object.SlotMainHand]; ok {
+ if def, err := g.ItemStore.Load(itemID); err == nil {
+ for _, t := range cfg.Tools {
+ if def.ToolType == t {
+ toolName = def.Name
+ break
+ }
+ }
+ }
+ }
+ if toolName == "" {
+ for _, slot := range p.Inventory {
+ if slot == nil || slot.Quantity <= 0 {
+ continue
+ }
+ def, err := g.ItemStore.Load(slot.ItemID)
+ if err != nil {
+ continue
+ }
+ for _, t := range cfg.Tools {
+ if def.ToolType == t {
+ toolName = def.Name
+ break
+ }
+ }
+ if toolName != "" {
+ break
+ }
+ }
+ }
+ p.ActionState = &ActionState{Type: ActionGathering, TargetName: obj.Name, ToolName: toolName}
+
data := map[string]any{
"behavior_id": obj.BehaviorID,
"instance_key": g.World.ObjStateKey(st.RoomID, st.DefID, st.Index),
@@ -135,7 +168,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
}
step := p.Action.Data["step"].(int)
- wait := p.Action.Data["effective_wait"].(int)
+ wait := p.Action.Data["effective_wait"].(float64)
instanceKey := p.Action.Data["instance_key"].(string)
_, shared := p.Action.Data["deplete_timer"]
@@ -153,21 +186,21 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
}
sess.WriteLine(fmt.Sprintf("\n%s", cfg.GatherMsg))
p.Action.Data["step"] = 1
- p.Action.WaitLeft = wait
+ p.Action.WaitLeft = g.computeTicks(wait)
return
}
if step == 2 {
st := g.World.GetObjStateByKey(instanceKey)
if st != nil && st.Depleted {
- p.Action.WaitLeft = 3
+ p.Action.WaitLeft = g.computeTicks(3)
return
}
if cfg.RespawnMsg != "" {
sess.WriteLine(fmt.Sprintf("\n%s", cfg.RespawnMsg))
}
p.Action.Data["step"] = 0
- p.Action.WaitLeft = wait
+ p.Action.WaitLeft = g.computeTicks(wait)
return
}
@@ -231,23 +264,30 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
g.checkBirdNest(sess, p, cfg, freeSlot)
- if shared {
- st := g.World.GetObjStateByKey(instanceKey)
- if st != nil && st.SharedTimer <= 0 {
- g.depleteSharedTree(sess, p, st, cfg, instanceKey)
- return
- }
+ if shared {
+ st := g.World.GetObjStateByKey(instanceKey)
+ if st != nil && st.SharedTimer <= 0 {
+ g.pendingDepletions = append(g.pendingDepletions, pendingDepletion{
+ instanceKey: instanceKey,
+ behaviorID: behaviorID,
+ targetName: p.Action.TargetName,
+ playerNames: []string{p.Name},
+ })
+ p.Action.Data["step"] = 2
+ p.Action.WaitLeft = g.computeTicks(1)
+ return
}
+ }
if !shared && drop.Depletes {
- delay := cfg.RespawnTimer
+ delay := g.computeTicks(cfg.RespawnTimer)
if delay <= 0 {
delay = 10
}
st := g.World.GetObjStateByKey(instanceKey)
if st != nil {
st.Depleted = true
- st.DepleteTimer = delay
+ st.DepleteTimer = float64(delay)
}
for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
@@ -265,7 +305,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
}
p.Action.Data["step"] = 2
- p.Action.WaitLeft = 1
+ p.Action.WaitLeft = g.computeTicks(1)
return
}
@@ -275,7 +315,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
return
}
p.Action.Data["step"] = 0
- p.Action.WaitLeft = wait
+ p.Action.WaitLeft = g.computeTicks(wait)
return
}
}
@@ -287,7 +327,7 @@ func (g *Game) advanceGather(sess *net.Session, p *player.Player) {
return
}
p.Action.Data["step"] = 0
- p.Action.WaitLeft = wait
+ p.Action.WaitLeft = g.computeTicks(wait)
}
func filterDropsByLevel(drops []action.DropEntry, level int) []action.DropEntry {
@@ -303,34 +343,68 @@ func filterDropsByLevel(drops []action.DropEntry, level int) []action.DropEntry
return eligible
}
-func (g *Game) depleteSharedTree(sess *net.Session, p *player.Player, st *world.ObjState, cfg *action.GatherConfig, instanceKey string) {
+func (g *Game) depleteSharedTree(st *world.ObjState, cfg *action.GatherConfig, targetName string, playerNames []string) {
st.Depleted = true
- st.DepleteTimer = cfg.RespawnTimer
+ st.DepleteTimer = float64(g.computeTicks(cfg.RespawnTimer))
st.SharedTimer = 0
msg := cfg.ExhaustedMessage
if msg == "" {
- msg = fmt.Sprintf("The %s falls to the ground!", p.Action.TargetName)
+ msg = fmt.Sprintf("The %s falls to the ground!", targetName)
}
- sess.WriteLine(fmt.Sprintf("\n%s", msg))
-
- for _, other := range g.Hub.PlayersInRoom(p.RoomID) {
- if other == sess {
+ for _, sess := range g.Hub.PlayersInRoom(st.RoomID) {
+ op, ok := sess.Player.(*player.Player)
+ if !ok || op.Action == nil || op.Action.Type != "gather" {
continue
}
- op, ok := other.Player.(*player.Player)
- if !ok || op.Action == nil || op.Action.Type != "gather" {
+ if op.Action.Data["instance_key"] != g.World.ObjStateKey(st.RoomID, st.DefID, st.Index) {
+ continue
+ }
+ isDepleter := false
+ for _, name := range playerNames {
+ if op.Name == name {
+ isDepleter = true
+ break
+ }
+ }
+ if isDepleter {
+ sess.WriteLine(fmt.Sprintf("\n%s", msg))
continue
}
- if op.Action.Data["instance_key"] == instanceKey {
- other.WriteLine(fmt.Sprintf("\n%s", msg))
- g.CancelAction(op)
+ sess.WriteLine(fmt.Sprintf("\n%s", msg))
+ g.CancelAction(op)
+ }
+}
+
+func (g *Game) flushPendingDepletions() {
+ if len(g.pendingDepletions) == 0 {
+ return
+ }
+
+ merged := make(map[string]*pendingDepletion)
+ for _, pd := range g.pendingDepletions {
+ if existing, ok := merged[pd.instanceKey]; ok {
+ existing.playerNames = append(existing.playerNames, pd.playerNames...)
+ } else {
+ copy := pd
+ merged[pd.instanceKey] = &copy
+ }
+ }
+
+ for _, pd := range merged {
+ st := g.World.GetObjStateByKey(pd.instanceKey)
+ if st == nil || st.Depleted {
+ continue
+ }
+ cfg, err := g.BehaviorStore.LoadGather(pd.behaviorID)
+ if err != nil {
+ continue
}
+ g.depleteSharedTree(st, cfg, pd.targetName, pd.playerNames)
}
- p.Action.Data["step"] = 2
- p.Action.WaitLeft = 1
+ g.pendingDepletions = nil
}
func (g *Game) checkBirdNest(sess *net.Session, p *player.Player, cfg *action.GatherConfig, currentDropSlot int) {