From 05cf59c65950ebae84fd5ef55822f97258af4b42 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Wed, 25 Mar 2026 14:21:46 +0900 Subject: [PATCH] feat: add Bleed and Curse status effects, add Freeze handler - Add StatusBleed and StatusCurse to the status effect enum - Rewrite TickEffects with index-based loop to support Bleed value mutation - Add Freeze tick message, Bleed damage (intensifies each turn), Curse message - Update Heal() to reduce healing amount when cursed - Add tests for Bleed stacking, Curse heal reduction, and Freeze tick message Co-Authored-By: Claude Opus 4.6 (1M context) --- entity/player.go | 38 ++++++++++++++++++++++++++------ entity/player_test.go | 51 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/entity/player.go b/entity/player.go index 939c7db..9965588 100644 --- a/entity/player.go +++ b/entity/player.go @@ -32,6 +32,8 @@ const ( StatusPoison StatusEffect = iota StatusBurn StatusFreeze + StatusBleed + StatusCurse ) type ActiveEffect struct { @@ -76,6 +78,12 @@ func (p *Player) TakeDamage(dmg int) { } func (p *Player) Heal(amount int) { + for _, e := range p.Effects { + if e.Type == StatusCurse { + amount = amount * (100 - e.Value) / 100 + break + } + } p.HP += amount if p.HP > p.MaxHP { p.HP = p.MaxHP @@ -157,29 +165,45 @@ func (p *Player) HasEffect(t StatusEffect) bool { return false } -func (p *Player) TickEffects() (damages []string) { - var remaining []ActiveEffect - for _, e := range p.Effects { +func (p *Player) TickEffects() []string { + var msgs []string + remaining := p.Effects[:0] // reuse underlying array + for i := 0; i < len(p.Effects); i++ { + e := &p.Effects[i] switch e.Type { case StatusPoison: p.HP -= e.Value if p.HP <= 0 { p.HP = 1 // Poison can't kill, leaves at 1 HP } - damages = append(damages, fmt.Sprintf("%s takes %d poison damage", p.Name, e.Value)) + msgs = append(msgs, fmt.Sprintf("%s takes %d poison damage", p.Name, e.Value)) case StatusBurn: p.HP -= e.Value if p.HP <= 0 { p.HP = 0 p.Dead = true } - damages = append(damages, fmt.Sprintf("%s takes %d burn damage", p.Name, e.Value)) + msgs = append(msgs, fmt.Sprintf("%s takes %d burn damage", p.Name, e.Value)) + case StatusFreeze: + msgs = append(msgs, fmt.Sprintf("%s is frozen!", p.Name)) + case StatusBleed: + p.HP -= e.Value + msgs = append(msgs, fmt.Sprintf("%s takes %d bleed damage", p.Name, e.Value)) + e.Value++ // Bleed intensifies each turn + case StatusCurse: + msgs = append(msgs, fmt.Sprintf("%s is cursed! Healing reduced", p.Name)) + } + if p.HP < 0 { + p.HP = 0 } e.Duration-- if e.Duration > 0 { - remaining = append(remaining, e) + remaining = append(remaining, *e) } } p.Effects = remaining - return + if p.HP <= 0 && !p.Dead { + p.Dead = true + } + return msgs } diff --git a/entity/player_test.go b/entity/player_test.go index 6b55cfe..630addb 100644 --- a/entity/player_test.go +++ b/entity/player_test.go @@ -1,6 +1,9 @@ package entity -import "testing" +import ( + "strings" + "testing" +) func TestNewPlayer(t *testing.T) { p := NewPlayer("testuser", ClassWarrior) @@ -190,3 +193,49 @@ func TestEffectOverwrite(t *testing.T) { t.Error("should have overwritten with new values") } } + +func TestBleedEffect(t *testing.T) { + p := NewPlayer("Test", ClassWarrior) + startHP := p.HP + p.AddEffect(ActiveEffect{Type: StatusBleed, Duration: 3, Value: 2}) + + msgs := p.TickEffects() + if len(msgs) == 0 || !strings.Contains(msgs[0], "bleed") { + t.Error("expected bleed damage message") + } + if p.HP != startHP-2 { + t.Errorf("expected HP %d, got %d", startHP-2, p.HP) + } + // After tick, remaining bleed should have value 3 (increased by 1) + if len(p.Effects) == 0 || p.Effects[0].Value != 3 { + t.Error("expected bleed value to increase to 3") + } +} + +func TestCurseReducesHealing(t *testing.T) { + p := NewPlayer("Test", ClassHealer) + p.HP = 50 + p.AddEffect(ActiveEffect{Type: StatusCurse, Duration: 3, Value: 50}) + p.Heal(100) + // Curse reduces by 50%, so heal 50 from HP 50 -> 100, capped at MaxHP + expected := p.MaxHP + if 50+50 < p.MaxHP { + expected = 50 + 50 + } + if p.HP != expected { + t.Errorf("expected HP %d, got %d", expected, p.HP) + } +} + +func TestFreezeTickMessage(t *testing.T) { + p := NewPlayer("Test", ClassMage) + p.AddEffect(ActiveEffect{Type: StatusFreeze, Duration: 1, Value: 0}) + msgs := p.TickEffects() + if len(msgs) == 0 || !strings.Contains(msgs[0], "frozen") { + t.Error("expected freeze message") + } + // Freeze duration 1 -> removed after tick + if len(p.Effects) != 0 { + t.Error("expected freeze to be removed after 1 tick") + } +}