diff options
| author | historia <[not public]> | 2026-07-17 17:12:39 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-17 17:12:39 -0400 |
| commit | 6e6c4a157564100249bb58f412485055bb2d10cf (patch) | |
| tree | d457414b989e737287384f87004bffa70ac23886 /internal/game/sys_energy.go | |
| parent | e09e7badbc722f580a9d7674c46d6ff3cc320b26 (diff) | |
| download | thehouseoficarus-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.go | 149 |
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 + } + } +} |
