fix: game balance — gold scaling, solo DEF, floor-scaled items, healer targeting, AI fallback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -97,14 +97,17 @@ func MonsterAI(m *entity.Monster, players []*entity.Player, turnNumber int) (tar
|
|||||||
}
|
}
|
||||||
if rand.Float64() < 0.3 {
|
if rand.Float64() < 0.3 {
|
||||||
minHP := int(^uint(0) >> 1)
|
minHP := int(^uint(0) >> 1)
|
||||||
minIdx := 0
|
minIdx := -1
|
||||||
for i, p := range players {
|
for i, p := range players {
|
||||||
if !p.IsDead() && p.HP < minHP {
|
if !p.IsDead() && p.HP < minHP {
|
||||||
minHP = p.HP
|
minHP = p.HP
|
||||||
minIdx = i
|
minIdx = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return minIdx, false
|
if minIdx >= 0 {
|
||||||
|
return minIdx, false
|
||||||
|
}
|
||||||
|
// Fall through to default targeting if no alive player found
|
||||||
}
|
}
|
||||||
for i, p := range players {
|
for i, p := range players {
|
||||||
if !p.IsDead() {
|
if !p.IsDead() {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ func (s *GameSession) spawnMonsters() {
|
|||||||
m.HP = 1
|
m.HP = 1
|
||||||
}
|
}
|
||||||
m.MaxHP = m.HP
|
m.MaxHP = m.HP
|
||||||
|
m.DEF = m.DEF / 2
|
||||||
}
|
}
|
||||||
s.state.Monsters[i] = m
|
s.state.Monsters[i] = m
|
||||||
}
|
}
|
||||||
@@ -111,21 +112,25 @@ func (s *GameSession) spawnBoss() {
|
|||||||
if s.state.SoloMode {
|
if s.state.SoloMode {
|
||||||
boss.HP = boss.HP / 2
|
boss.HP = boss.HP / 2
|
||||||
boss.MaxHP = boss.HP
|
boss.MaxHP = boss.HP
|
||||||
|
boss.DEF = boss.DEF / 2
|
||||||
}
|
}
|
||||||
s.state.Monsters = []*entity.Monster{boss}
|
s.state.Monsters = []*entity.Monster{boss}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameSession) grantTreasure() {
|
func (s *GameSession) grantTreasure() {
|
||||||
|
floor := s.state.FloorNum
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if rand.Float64() < 0.5 {
|
if rand.Float64() < 0.5 {
|
||||||
|
bonus := 3 + rand.Intn(6) + floor/3
|
||||||
item := entity.Item{
|
item := entity.Item{
|
||||||
Name: "Iron Sword", Type: entity.ItemWeapon, Bonus: 3 + rand.Intn(6),
|
Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (ATK+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s found %s (ATK+%d)", p.Name, item.Name, item.Bonus))
|
||||||
} else {
|
} else {
|
||||||
|
bonus := 2 + rand.Intn(4) + floor/4
|
||||||
item := entity.Item{
|
item := entity.Item{
|
||||||
Name: "Iron Shield", Type: entity.ItemArmor, Bonus: 2 + rand.Intn(4),
|
Name: armorName(floor), Type: entity.ItemArmor, Bonus: bonus,
|
||||||
}
|
}
|
||||||
p.Inventory = append(p.Inventory, item)
|
p.Inventory = append(p.Inventory, item)
|
||||||
s.addLog(fmt.Sprintf("%s found %s (DEF+%d)", p.Name, item.Name, item.Bonus))
|
s.addLog(fmt.Sprintf("%s found %s (DEF+%d)", p.Name, item.Name, item.Bonus))
|
||||||
@@ -134,10 +139,48 @@ func (s *GameSession) grantTreasure() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *GameSession) generateShopItems() {
|
func (s *GameSession) generateShopItems() {
|
||||||
|
floor := s.state.FloorNum
|
||||||
|
// Weapon bonus scales: base 3-8 + floor/3
|
||||||
|
weaponBonus := 3 + rand.Intn(6) + floor/3
|
||||||
|
// Armor bonus scales: base 2-5 + floor/4
|
||||||
|
armorBonus := 2 + rand.Intn(4) + floor/4
|
||||||
|
// Prices scale with power
|
||||||
|
weaponPrice := 40 + weaponBonus*5
|
||||||
|
armorPrice := 30 + armorBonus*5
|
||||||
|
// Potion heals more on higher floors
|
||||||
|
potionHeal := 30 + floor
|
||||||
|
potionPrice := 20 + floor/2
|
||||||
|
|
||||||
s.state.ShopItems = []entity.Item{
|
s.state.ShopItems = []entity.Item{
|
||||||
{Name: "HP Potion", Type: entity.ItemConsumable, Bonus: 30, Price: 20},
|
{Name: "HP Potion", Type: entity.ItemConsumable, Bonus: potionHeal, Price: potionPrice},
|
||||||
{Name: "Iron Sword", Type: entity.ItemWeapon, Bonus: 3 + rand.Intn(6), Price: 40 + rand.Intn(41)},
|
{Name: weaponName(floor), Type: entity.ItemWeapon, Bonus: weaponBonus, Price: weaponPrice},
|
||||||
{Name: "Iron Shield", Type: entity.ItemArmor, Bonus: 2 + rand.Intn(4), Price: 30 + rand.Intn(31)},
|
{Name: armorName(floor), Type: entity.ItemArmor, Bonus: armorBonus, Price: armorPrice},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func weaponName(floor int) string {
|
||||||
|
switch {
|
||||||
|
case floor >= 15:
|
||||||
|
return "Mythril Blade"
|
||||||
|
case floor >= 10:
|
||||||
|
return "Steel Sword"
|
||||||
|
case floor >= 5:
|
||||||
|
return "Bronze Sword"
|
||||||
|
default:
|
||||||
|
return "Iron Sword"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func armorName(floor int) string {
|
||||||
|
switch {
|
||||||
|
case floor >= 15:
|
||||||
|
return "Mythril Shield"
|
||||||
|
case floor >= 10:
|
||||||
|
return "Steel Shield"
|
||||||
|
case floor >= 5:
|
||||||
|
return "Bronze Shield"
|
||||||
|
default:
|
||||||
|
return "Iron Shield"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
game/turn.go
17
game/turn.go
@@ -117,9 +117,19 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
case entity.ClassHealer:
|
case entity.ClassHealer:
|
||||||
targetIdx := action.TargetIdx
|
targetIdx := action.TargetIdx
|
||||||
if targetIdx < 0 || targetIdx >= len(s.state.Players) {
|
if targetIdx < 0 || targetIdx >= len(s.state.Players) {
|
||||||
targetIdx = 0 // heal self by default
|
targetIdx = 0
|
||||||
}
|
}
|
||||||
target := s.state.Players[targetIdx]
|
target := s.state.Players[targetIdx]
|
||||||
|
if target.IsDead() {
|
||||||
|
// Find first alive player to heal instead
|
||||||
|
for j, candidate := range s.state.Players {
|
||||||
|
if !candidate.IsOut() {
|
||||||
|
target = candidate
|
||||||
|
targetIdx = j
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
before := target.HP
|
before := target.HP
|
||||||
target.Heal(30)
|
target.Heal(30)
|
||||||
s.addLog(fmt.Sprintf("%s healed %s for %d HP", p.Name, target.Name, target.HP-before))
|
s.addLog(fmt.Sprintf("%s healed %s for %d HP", p.Name, target.Name, target.HP-before))
|
||||||
@@ -203,10 +213,7 @@ func (s *GameSession) resolvePlayerActions() {
|
|||||||
// Award gold only for monsters that JUST died this turn
|
// Award gold only for monsters that JUST died this turn
|
||||||
for i, m := range s.state.Monsters {
|
for i, m := range s.state.Monsters {
|
||||||
if m.IsDead() && aliveBeforeTurn[i] {
|
if m.IsDead() && aliveBeforeTurn[i] {
|
||||||
goldReward := 5 + s.state.FloorNum
|
goldReward := 5 + s.state.FloorNum*2
|
||||||
if goldReward > 15 {
|
|
||||||
goldReward = 15
|
|
||||||
}
|
|
||||||
for _, p := range s.state.Players {
|
for _, p := range s.state.Players {
|
||||||
if !p.IsOut() {
|
if !p.IsOut() {
|
||||||
bonus := 0
|
bonus := 0
|
||||||
|
|||||||
Reference in New Issue
Block a user