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 } } }