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_test.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_test.go')
| -rw-r--r-- | internal/game/sys_energy_test.go | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/internal/game/sys_energy_test.go b/internal/game/sys_energy_test.go new file mode 100644 index 0000000..ff897f0 --- /dev/null +++ b/internal/game/sys_energy_test.go @@ -0,0 +1,250 @@ +package game + +import ( + "testing" + + "thehouseoficarus/internal/combat" + "thehouseoficarus/internal/game/hacking" + "thehouseoficarus/internal/item" + "thehouseoficarus/internal/net" + "thehouseoficarus/internal/player" +) + +func energyTestGame() *Game { + return &Game{ + Combat: combat.NewTracker(), + safespot: NewSafespotManager(), + hackingStates: make(map[string]*hacking.Session), + Hub: net.NewHub(), + } +} + +// addSession registers a player session with the test game's hub so that +// EnergyRegenTick iterates over it. +func addSession(g *Game, p *player.Player) { + sess := &net.Session{Player: p} + g.Hub.Add(sess) +} + +func TestMaxRunEnergy(t *testing.T) { + p := player.New("tester") + if p.MaxRunEnergy() != 3 { // agility level 1 + t.Fatalf("fresh player max run energy = %d, want 3", p.MaxRunEnergy()) + } + if p.RunEnergy != 3 { + t.Fatalf("fresh player run energy not initialized to max: %d", p.RunEnergy) + } + p.Skills[player.Agility] = player.XPForLevel(20) + if p.MaxRunEnergy() != 60 { + t.Fatalf("agility 20 max run energy = %d, want 60", p.MaxRunEnergy()) + } +} + +func TestMoveTicks(t *testing.T) { + g := energyTestGame() + + // Single move, energy present -> 1 tick, uses energy. + p := player.New("a") + if ticks := g.moveTicks(p, false); ticks != 1 { + t.Errorf("single move with energy: ticks=%d want 1", ticks) + } else if !p.MoveUsesEnergy { + t.Error("single move with energy should set MoveUsesEnergy") + } + + // Single move, no energy -> 2 ticks, no drain. + p2 := player.New("b") + p2.RunEnergy = 0 + if ticks := g.moveTicks(p2, false); ticks != 2 { + t.Errorf("single move no energy: ticks=%d want 2", ticks) + } else if p2.MoveUsesEnergy { + t.Error("single move no energy should not set MoveUsesEnergy") + } + + // Walk without full graceful set -> 2 ticks, no drain. + p3 := player.New("c") + p3.RunEnergy = 10 + if ticks := g.moveTicks(p3, true); ticks != 2 { + t.Errorf("walk no full set: ticks=%d want 2", ticks) + } else if p3.MoveUsesEnergy { + t.Error("walk without full set should not drain energy") + } + + // Cape of Agility -> 0 ticks, no drain regardless of walk. + p4 := player.New("d") + p4.Equipment[item.SlotBack] = capeOfAgilityID + if ticks := g.moveTicks(p4, false); ticks != 0 { + t.Errorf("cape single move: ticks=%d want 0", ticks) + } else if p4.MoveUsesEnergy { + t.Error("cape should not drain energy") + } + if ticks := g.moveTicks(p4, true); ticks != 0 { + t.Errorf("cape walk: ticks=%d want 0", ticks) + } + + // GodMode -> 0 ticks, no drain. + p5 := player.New("e") + p5.GodMode = true + if ticks := g.moveTicks(p5, false); ticks != 0 { + t.Errorf("godmode: ticks=%d want 0", ticks) + } else if p5.MoveUsesEnergy { + t.Error("godmode should not drain energy") + } + + // Full graceful set, walk, with energy -> 1 tick, drains. + p6 := player.New("f") + p6.Equipment[item.SlotBack] = "graceful_cape" + p6.Equipment[item.SlotHead] = "graceful_hat" + p6.Equipment[item.SlotTorso] = "graceful_torso" + p6.Equipment[item.SlotLegs] = "graceful_legs" + p6.Equipment[item.SlotHands] = "graceful_gloves" + p6.Equipment[item.SlotFeet] = "graceful_boots" + if got := gracefulCount(p6); got != 6 { + t.Fatalf("gracefulCount=%d want 6", got) + } + if ticks := g.moveTicks(p6, true); ticks != 1 { + t.Errorf("full-set walk with energy: ticks=%d want 1", ticks) + } else if !p6.MoveUsesEnergy { + t.Error("full-set walk with energy should drain") + } + + // Full graceful set, walk, no energy -> 2 ticks, no drain. + p7 := player.New("g") + for slot, id := range p6.Equipment { + p7.Equipment[slot] = id + } + p7.RunEnergy = 0 + if ticks := g.moveTicks(p7, true); ticks != 2 { + t.Errorf("full-set walk no energy: ticks=%d want 2", ticks) + } else if p7.MoveUsesEnergy { + t.Error("full-set walk with no energy should not drain") + } +} + +func TestGracefulCountCapeExcludesFullSet(t *testing.T) { + // Cape of Agility shares the back slot — 5 graceful pieces + cape = 5, + // not 6, so no full-set bonus. + p := player.New("h") + p.Equipment[item.SlotBack] = capeOfAgilityID + p.Equipment[item.SlotHead] = "graceful_hat" + p.Equipment[item.SlotTorso] = "graceful_torso" + p.Equipment[item.SlotLegs] = "graceful_legs" + p.Equipment[item.SlotHands] = "graceful_gloves" + p.Equipment[item.SlotFeet] = "graceful_boots" + if got := gracefulCount(p); got != 5 { + t.Errorf("5 graceful + cape: count=%d want 5", got) + } + if hasCapeOfAgility(p) != true { + t.Error("should detect cape of agility") + } +} + +func TestEnergyRegenIdle(t *testing.T) { + g := energyTestGame() + p := player.New("idle") + p.RunEnergy = 0 + p.Skills[player.Agility] = player.XPForLevel(10) // max 30 + addSession(g, p) + + // Idle baseline: 1 energy per 5 ticks. + for i := 0; i < 4; i++ { + g.EnergyRegenTick() + } + if p.RunEnergy != 0 { + t.Errorf("after 4 idle ticks energy=%d want 0", p.RunEnergy) + } + g.EnergyRegenTick() // 5th tick + if p.RunEnergy != 1 { + t.Errorf("after 5 idle ticks energy=%d want 1", p.RunEnergy) + } +} + +func TestEnergyRegenBusyCombat(t *testing.T) { + g := energyTestGame() + p := player.New("busy") + p.RunEnergy = 0 + p.Skills[player.Agility] = player.XPForLevel(10) + addSession(g, p) + // Mark busy via combat tracker. + g.Combat.Enter(p.Name, "mob1") + + // Busy baseline: 1 energy per 10 ticks. + for i := 0; i < 9; i++ { + g.EnergyRegenTick() + } + if p.RunEnergy != 0 { + t.Errorf("after 9 busy ticks energy=%d want 0", p.RunEnergy) + } + g.EnergyRegenTick() // 10th + if p.RunEnergy != 1 { + t.Errorf("after 10 busy ticks energy=%d want 1", p.RunEnergy) + } +} + +func TestEnergyRegenGracefulBonus(t *testing.T) { + g := energyTestGame() + p := player.New("graceful") + p.RunEnergy = 0 + p.Skills[player.Agility] = player.XPForLevel(10) + p.Equipment[item.SlotBack] = "graceful_cape" + p.Equipment[item.SlotHead] = "graceful_hat" + p.Equipment[item.SlotTorso] = "graceful_torso" + p.Equipment[item.SlotLegs] = "graceful_legs" + p.Equipment[item.SlotHands] = "graceful_gloves" + p.Equipment[item.SlotFeet] = "graceful_boots" + addSession(g, p) + + // Full set idle rate = (1/5) * 1.48 * 1.10 = ~0.3256 per tick. + // First point should arrive between tick 3 and 5. + totalTicks := 0 + for p.RunEnergy == 0 && totalTicks < 10 { + g.EnergyRegenTick() + totalTicks++ + } + if totalTicks > 5 { + t.Errorf("full-set regen too slow: took %d ticks for first point", totalTicks) + } + if totalTicks < 3 { + t.Errorf("full-set regen unexpectedly fast: %d ticks", totalTicks) + } +} + +func TestEnergyRegenCapsAtMax(t *testing.T) { + g := energyTestGame() + p := player.New("cap") + p.Skills[player.Agility] = player.XPForLevel(5) // max 15 + p.RunEnergy = 15 + addSession(g, p) + g.EnergyRegenTick() + if p.RunEnergy != 15 { + t.Errorf("energy above max not capped: %d want 15", p.RunEnergy) + } + if p.RunEnergyAccum != 0 { + t.Errorf("accumulator not reset at max: %v", p.RunEnergyAccum) + } +} + +func TestClampRunEnergyOnAgilityLevelUp(t *testing.T) { + p := player.New("clamp") + p.Skills[player.Agility] = player.XPForLevel(10) // max 30 + p.RunEnergy = 30 // at max + + // Grant agility XP to reach level 11 (max 33). Energy stays at 30 (no auto-fill). + p.AddXP(player.Agility, player.XPForLevel(11)-p.Skills[player.Agility]) + p.ClampRunEnergy() + if p.Level(player.Agility) != 11 { + t.Fatalf("expected agility 11, got %d", p.Level(player.Agility)) + } + if p.RunEnergy != 30 { + t.Errorf("level-up should not auto-fill energy: got %d want 30", p.RunEnergy) + } + if p.MaxRunEnergy() != 33 { + t.Errorf("max after level-up = %d want 33", p.MaxRunEnergy()) + } + + // If somehow over max, it clamps down. + p.RunEnergy = 50 + p.ClampRunEnergy() + if p.RunEnergy != 33 { + t.Errorf("clamp down failed: %d want 33", p.RunEnergy) + } +} |
