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) <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,8 @@ const (
|
|||||||
StatusPoison StatusEffect = iota
|
StatusPoison StatusEffect = iota
|
||||||
StatusBurn
|
StatusBurn
|
||||||
StatusFreeze
|
StatusFreeze
|
||||||
|
StatusBleed
|
||||||
|
StatusCurse
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActiveEffect struct {
|
type ActiveEffect struct {
|
||||||
@@ -76,6 +78,12 @@ func (p *Player) TakeDamage(dmg int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) Heal(amount 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
|
p.HP += amount
|
||||||
if p.HP > p.MaxHP {
|
if p.HP > p.MaxHP {
|
||||||
p.HP = p.MaxHP
|
p.HP = p.MaxHP
|
||||||
@@ -157,29 +165,45 @@ func (p *Player) HasEffect(t StatusEffect) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Player) TickEffects() (damages []string) {
|
func (p *Player) TickEffects() []string {
|
||||||
var remaining []ActiveEffect
|
var msgs []string
|
||||||
for _, e := range p.Effects {
|
remaining := p.Effects[:0] // reuse underlying array
|
||||||
|
for i := 0; i < len(p.Effects); i++ {
|
||||||
|
e := &p.Effects[i]
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
case StatusPoison:
|
case StatusPoison:
|
||||||
p.HP -= e.Value
|
p.HP -= e.Value
|
||||||
if p.HP <= 0 {
|
if p.HP <= 0 {
|
||||||
p.HP = 1 // Poison can't kill, leaves at 1 HP
|
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:
|
case StatusBurn:
|
||||||
p.HP -= e.Value
|
p.HP -= e.Value
|
||||||
if p.HP <= 0 {
|
if p.HP <= 0 {
|
||||||
p.HP = 0
|
p.HP = 0
|
||||||
p.Dead = true
|
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--
|
e.Duration--
|
||||||
if e.Duration > 0 {
|
if e.Duration > 0 {
|
||||||
remaining = append(remaining, e)
|
remaining = append(remaining, *e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Effects = remaining
|
p.Effects = remaining
|
||||||
return
|
if p.HP <= 0 && !p.Dead {
|
||||||
|
p.Dead = true
|
||||||
|
}
|
||||||
|
return msgs
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestNewPlayer(t *testing.T) {
|
func TestNewPlayer(t *testing.T) {
|
||||||
p := NewPlayer("testuser", ClassWarrior)
|
p := NewPlayer("testuser", ClassWarrior)
|
||||||
@@ -190,3 +193,49 @@ func TestEffectOverwrite(t *testing.T) {
|
|||||||
t.Error("should have overwritten with new values")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user