aboutsummaryrefslogtreecommitdiff
path: root/internal/game/sys_energy.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-17 17:12:39 -0400
committerhistoria <[not public]>2026-07-17 17:12:39 -0400
commit6e6c4a157564100249bb58f412485055bb2d10cf (patch)
treed457414b989e737287384f87004bffa70ac23886 /internal/game/sys_energy.go
parente09e7badbc722f580a9d7674c46d6ff3cc320b26 (diff)
downloadthehouseoficarus-6e6c4a157564100249bb58f412485055bb2d10cf.tar.gz
feat: implement run energy, shorten walk time to 1-2 ticks between rooms, update graceful set
Diffstat (limited to 'internal/game/sys_energy.go')
-rw-r--r--internal/game/sys_energy.go149
1 files changed, 149 insertions, 0 deletions
diff --git a/internal/game/sys_energy.go b/internal/game/sys_energy.go
new file mode 100644
index 0000000..7331fa8
--- /dev/null
+++ b/internal/game/sys_energy.go
@@ -0,0 +1,149 @@
+package game
+
+import (
+ "thehouseoficarus/internal/item"
+ "thehouseoficarus/internal/player"
+)
+
+// gracefulItems is the set of equipment IDs that count toward the Graceful
+// outfit. Wearing pieces of the set increases run-energy regeneration speed
+// (see EnergyRegenTick); wearing the complete 6-piece set grants an additional
+// bonus. The Cape of Agility and graceful_cape share the back slot and are
+// therefore mutually exclusive, so the full-set bonus requires graceful_cape.
+var gracefulItems = map[string]bool{
+ "graceful_hat": true,
+ "graceful_torso": true,
+ "graceful_legs": true,
+ "graceful_gloves": true,
+ "graceful_boots": true,
+ "graceful_cape": true,
+}
+
+const capeOfAgilityID = "cape_of_agility"
+
+// gracefulCount returns how many pieces of the Graceful outfit the player is
+// currently wearing (maximum 6).
+func gracefulCount(p *player.Player) int {
+ count := 0
+ for _, id := range p.Equipment {
+ if gracefulItems[id] {
+ count++
+ }
+ }
+ return count
+}
+
+// hasCapeOfAgility reports whether the Cape of Agility is worn in the back
+// slot. The cape grants infinite run energy (movement is instant and never
+// drains energy).
+func hasCapeOfAgility(p *player.Player) bool {
+ id, ok := p.Equipment[item.SlotBack]
+ return ok && id == capeOfAgilityID
+}
+
+// moveTicks returns how many ticks the upcoming move will take, and sets
+// p.MoveUsesEnergy to indicate whether completing the move should drain one
+// point of run energy.
+//
+// - GodMode and Cape of Agility: instant (0 ticks), never drains energy.
+// - A single move (north/e/w...): 1 tick if energy remains, otherwise 2.
+// - The "walk" command: 2 ticks per step and never drains energy unless the
+// player wears the complete Graceful outfit (or Cape of Agility), in which
+// case it behaves like a single run move (1 tick with energy, 2 without).
+func (g *Game) moveTicks(p *player.Player, isWalk bool) int {
+ if p.GodMode {
+ p.MoveUsesEnergy = false
+ return 0
+ }
+ if hasCapeOfAgility(p) {
+ p.MoveUsesEnergy = false
+ return 0
+ }
+
+ if isWalk && gracefulCount(p) != 6 {
+ // Plain walk: slow, no energy cost.
+ p.MoveUsesEnergy = false
+ return 2
+ }
+
+ // Single move, or a run-walk enabled by the full Graceful set.
+ if p.HasRunEnergy() {
+ p.MoveUsesEnergy = true
+ return 1
+ }
+ p.MoveUsesEnergy = false
+ return 2
+}
+
+// isPlayerBusy reports whether the player is currently performing an action
+// (combat, gathering, crafting, walking, hiding, hacking, etc.). A busy player
+// regenerates run energy twice as slowly as an idle one.
+func (g *Game) isPlayerBusy(p *player.Player) bool {
+ if p.Action != nil || p.BackgroundAction != nil {
+ return true
+ }
+ if g.Combat.Get(p.Name) != nil {
+ return true
+ }
+ if p.MoveTicks > 0 || len(p.WalkSequence) > 0 {
+ return true
+ }
+ if _, ok := g.safespot.Get(p.Name); ok {
+ return true
+ }
+ if _, ok := g.hackingStates[p.Name]; ok {
+ return true
+ }
+ return false
+}
+
+// EnergyRegenTick advances run-energy regeneration for every online player.
+//
+// Idle: 1 energy per 5 ticks
+// Busy: 1 energy per 10 ticks
+// Graceful: +8% recovery rate per equipped piece
+// full 6-piece set adds a further +10%
+//
+// Fractional recovery is accumulated across ticks in thousandths of an energy
+// point (RunEnergyAccum, int) so that per-tick float drift never prevents a
+// point from arriving exactly when its rate says it should.
+func (g *Game) EnergyRegenTick() {
+ if g.Hub == nil {
+ return
+ }
+ const perEnergy = 1000
+ for _, sess := range g.Hub.AllSessions() {
+ p := sess.Player
+ if p == nil {
+ continue
+ }
+ maxE := p.MaxRunEnergy()
+ if maxE <= 0 || p.RunEnergy >= maxE {
+ p.RunEnergyAccum = 0
+ continue
+ }
+
+ // Base recovery rate in thousandths of an energy point per tick.
+ base := perEnergy / 5 // idle: 1/5
+ if g.isPlayerBusy(p) {
+ base = perEnergy / 10 // busy: 1/10
+ }
+
+ // Graceful: +8% per piece, +10% bonus for the complete 6-piece set.
+ count := gracefulCount(p)
+ mult := 1.0 + 0.08*float64(count)
+ if count == 6 {
+ mult *= 1.10
+ }
+
+ perTick := int(float64(base)*mult + 0.5) // round to nearest
+ p.RunEnergyAccum += perTick
+ for p.RunEnergyAccum >= perEnergy && p.RunEnergy < maxE {
+ p.RunEnergyAccum -= perEnergy
+ p.RunEnergy++
+ }
+ if p.RunEnergy >= maxE {
+ p.RunEnergyAccum = 0
+ }
+ }
+}