1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
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 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).
//
// The caller is responsible for storing the returned usesEnergy flag into
// p.MoveUsesEnergy only when a move is genuinely about to enter flight — never
// while a move is merely being stashed as a pending flee.
func (g *Game) moveTicks(p *player.Player, isWalk bool) (int, bool) {
if p.GodMode {
return 0, false
}
if hasCapeOfAgility(p) {
return 0, false
}
if isWalk && gracefulCount(p) != 6 {
return 2, false
}
if p.HasRunEnergy() {
return 1, true
}
return 2, false
}
// 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
}
}
}
|