feat: add floor themes with status effect modifiers
Add 4 floor themes (Swamp/Volcano/Glacier/Inferno) that boost status effect damage on matching floors. Realign boss patterns to match themes and add PatternFreeze for the Glacier boss. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
32
dungeon/theme.go
Normal file
32
dungeon/theme.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package dungeon
|
||||
|
||||
import "github.com/tolelom/catacombs/entity"
|
||||
|
||||
// ThemeModifier defines gameplay modifiers for a range of floors.
|
||||
type ThemeModifier struct {
|
||||
Name string
|
||||
StatusBoost entity.StatusEffect // which status is boosted (-1 = all for Inferno)
|
||||
DamageMult float64
|
||||
Description string
|
||||
}
|
||||
|
||||
var themeModifiers = []ThemeModifier{
|
||||
{"Swamp", entity.StatusPoison, 1.5, "Toxic marshes amplify poison"},
|
||||
{"Volcano", entity.StatusBurn, 1.5, "Volcanic heat intensifies burns"},
|
||||
{"Glacier", entity.StatusFreeze, 1.5, "Glacial cold strengthens frost"},
|
||||
{"Inferno", entity.StatusEffect(-1), 1.3, "Hellfire empowers all afflictions"},
|
||||
}
|
||||
|
||||
// GetTheme returns the theme modifier for the given floor number.
|
||||
func GetTheme(floor int) ThemeModifier {
|
||||
switch {
|
||||
case floor <= 5:
|
||||
return themeModifiers[0]
|
||||
case floor <= 10:
|
||||
return themeModifiers[1]
|
||||
case floor <= 15:
|
||||
return themeModifiers[2]
|
||||
default:
|
||||
return themeModifiers[3]
|
||||
}
|
||||
}
|
||||
32
dungeon/theme_test.go
Normal file
32
dungeon/theme_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package dungeon
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGetTheme(t *testing.T) {
|
||||
tests := []struct {
|
||||
floor int
|
||||
name string
|
||||
}{
|
||||
{1, "Swamp"}, {5, "Swamp"},
|
||||
{6, "Volcano"}, {10, "Volcano"},
|
||||
{11, "Glacier"}, {15, "Glacier"},
|
||||
{16, "Inferno"}, {20, "Inferno"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
theme := GetTheme(tt.floor)
|
||||
if theme.Name != tt.name {
|
||||
t.Errorf("floor %d: expected %q, got %q", tt.floor, tt.name, theme.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestThemeDamageMult(t *testing.T) {
|
||||
theme := GetTheme(1) // Swamp
|
||||
if theme.DamageMult != 1.5 {
|
||||
t.Errorf("Swamp DamageMult: expected 1.5, got %f", theme.DamageMult)
|
||||
}
|
||||
theme = GetTheme(20) // Inferno
|
||||
if theme.DamageMult != 1.3 {
|
||||
t.Errorf("Inferno DamageMult: expected 1.3, got %f", theme.DamageMult)
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,7 @@ const (
|
||||
PatternPoison // applies poison
|
||||
PatternBurn // applies burn to random player
|
||||
PatternHeal // heals self
|
||||
PatternFreeze // applies freeze to all players
|
||||
)
|
||||
|
||||
type Monster struct {
|
||||
|
||||
@@ -119,13 +119,13 @@ func (s *GameSession) spawnBoss() {
|
||||
boss := entity.NewMonster(mt, s.state.FloorNum, s.cfg.Combat.MonsterScaling)
|
||||
switch mt {
|
||||
case entity.MonsterBoss5:
|
||||
boss.Pattern = entity.PatternAoE
|
||||
boss.Pattern = entity.PatternPoison // Swamp theme
|
||||
case entity.MonsterBoss10:
|
||||
boss.Pattern = entity.PatternPoison
|
||||
boss.Pattern = entity.PatternBurn // Volcano theme
|
||||
case entity.MonsterBoss15:
|
||||
boss.Pattern = entity.PatternBurn
|
||||
boss.Pattern = entity.PatternFreeze // Glacier theme
|
||||
case entity.MonsterBoss20:
|
||||
boss.Pattern = entity.PatternHeal
|
||||
boss.Pattern = entity.PatternHeal // Inferno theme (+ natural AoE every 3 turns)
|
||||
}
|
||||
if s.state.SoloMode {
|
||||
boss.HP = int(float64(boss.HP) * s.cfg.Combat.SoloHPReduction)
|
||||
|
||||
26
game/turn.go
26
game/turn.go
@@ -72,13 +72,30 @@ collecting:
|
||||
}
|
||||
|
||||
func (s *GameSession) resolvePlayerActions() {
|
||||
// Tick status effects
|
||||
// Tick status effects with floor theme damage bonus
|
||||
theme := dungeon.GetTheme(s.state.FloorNum)
|
||||
for _, p := range s.state.Players {
|
||||
if !p.IsOut() {
|
||||
// Snapshot effects before tick to compute theme bonus
|
||||
effectsBefore := make([]entity.ActiveEffect, len(p.Effects))
|
||||
copy(effectsBefore, p.Effects)
|
||||
|
||||
msgs := p.TickEffects()
|
||||
for _, msg := range msgs {
|
||||
s.addLog(msg)
|
||||
}
|
||||
|
||||
// Apply theme damage bonus for matching status effects
|
||||
for _, e := range effectsBefore {
|
||||
if e.Value > 0 && (theme.StatusBoost == entity.StatusEffect(-1) || e.Type == theme.StatusBoost) {
|
||||
bonus := int(float64(e.Value) * (theme.DamageMult - 1.0))
|
||||
if bonus > 0 {
|
||||
p.TakeDamage(bonus)
|
||||
s.addLog(fmt.Sprintf(" (%s theme: +%d damage)", theme.Name, bonus))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.IsDead() {
|
||||
s.addLog(fmt.Sprintf("☠ %s has fallen!", p.Name))
|
||||
}
|
||||
@@ -409,6 +426,13 @@ func (s *GameSession) resolveMonsterActions() {
|
||||
s.addLog(fmt.Sprintf("%s burns %s!", m.Name, p.Name))
|
||||
}
|
||||
}
|
||||
case entity.PatternFreeze:
|
||||
for _, p := range s.state.Players {
|
||||
if !p.IsOut() {
|
||||
p.AddEffect(entity.ActiveEffect{Type: entity.StatusFreeze, Duration: 1, Value: 0})
|
||||
s.addLog(fmt.Sprintf("%s freezes %s!", m.Name, p.Name))
|
||||
}
|
||||
}
|
||||
case entity.PatternHeal:
|
||||
healAmt := m.MaxHP / 10
|
||||
m.HP += healAmt
|
||||
|
||||
Reference in New Issue
Block a user