mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 00:52:40 +00:00
Combat: per-round !cast / !consume in turn-based fights
Turn-based Elite/Boss fights gain !cast and !consume as player-turn actions, so casters and item-users make decisions per round instead of pre-queuing a single effect. The command handler validates and resolves the spell/item into a pre-rolled turnActionEffect; the engine just applies the HP deltas and flows on into the enemy turn. Scoped to effects that resolve within the casting round: damage, heal, and control spells, plus heal/flat-damage consumables. Buff and utility spells and buff-type consumables are refused without spending the resource — they need cross-round stat persistence, a later sub-phase. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -290,6 +290,90 @@ func applySpellBuff(spell SpellDefinition, c *DnDCharacter, stats *CombatStats,
|
||||
}
|
||||
}
|
||||
|
||||
// ── Phase 13 — per-round turn-based casting ──────────────────────────────────
|
||||
|
||||
// turnSpellOutcome is the resolved effect of a spell cast as a player turn in a
|
||||
// turn-based fight. Damage, healing, and the one-round enemy skip all land
|
||||
// within the casting round, so no cross-round persistence is needed.
|
||||
type turnSpellOutcome struct {
|
||||
EnemyDamage int
|
||||
PlayerHeal int
|
||||
EnemySkip bool
|
||||
Desc string
|
||||
}
|
||||
|
||||
// resolveTurnSpell resolves a spell as a turn-based player action, reusing the
|
||||
// auto-resolve spell math against a throwaway modifier set. supported is false
|
||||
// for buff and utility spells: those mutate stats for the rest of the fight,
|
||||
// which the turn engine can't carry yet (it rebuilds combatants each round) —
|
||||
// the caller should refuse them without spending the slot. Reaction spells are
|
||||
// likewise out of scope here and should be filtered by the caller.
|
||||
func resolveTurnSpell(c *DnDCharacter, spell SpellDefinition, slotLevel int, enemy *CombatStats) (out turnSpellOutcome, supported bool) {
|
||||
switch spell.Effect {
|
||||
case EffectSpellHeal:
|
||||
out.PlayerHeal = rollTurnSpellHeal(c, spell, slotLevel)
|
||||
out.Desc = fmt.Sprintf("%s — +%d HP", spell.Name, out.PlayerHeal)
|
||||
return out, true
|
||||
case EffectDamageAttack, EffectDamageSave, EffectDamageAuto, EffectControl:
|
||||
var mods CombatModifiers
|
||||
dc := spellSaveDC(c)
|
||||
atk := spellAttackBonus(c)
|
||||
switch spell.Effect {
|
||||
case EffectDamageAttack:
|
||||
applySpellDamageAttack(spell, atk, &mods, enemy, slotLevel, c.Level)
|
||||
case EffectDamageSave:
|
||||
applySpellDamageSave(spell, dc, c, &mods, enemy, slotLevel)
|
||||
case EffectDamageAuto:
|
||||
applySpellDamageAuto(spell, &mods, slotLevel, c.Level)
|
||||
case EffectControl:
|
||||
applySpellControl(spell, dc, &mods, enemy, slotLevel)
|
||||
}
|
||||
if mods.SpellPreDamage > 0 {
|
||||
applyMageSubclassSpellHooks(c, spell, slotLevel, &mods)
|
||||
}
|
||||
out.EnemyDamage = mods.SpellPreDamage
|
||||
out.EnemySkip = mods.SpellEnemySkipFirst
|
||||
out.Desc = mods.SpellPreDamageDesc
|
||||
if out.Desc == "" {
|
||||
out.Desc = spell.Name
|
||||
}
|
||||
return out, true
|
||||
default:
|
||||
// EffectBuffSelf / EffectBuffAlly / EffectUtility — deferred.
|
||||
return out, false
|
||||
}
|
||||
}
|
||||
|
||||
// rollTurnSpellHeal rolls an in-combat healing cast. Mirrors
|
||||
// resolveHealOutOfCombat's formula but returns the amount instead of writing
|
||||
// to the character sheet — in a turn-based fight, HP lives on the session row.
|
||||
func rollTurnSpellHeal(c *DnDCharacter, spell SpellDefinition, slotLevel int) int {
|
||||
dice, faces, _ := parseDamageDice(spell.DamageDice)
|
||||
if dice == 0 {
|
||||
dice, faces = 1, 8
|
||||
}
|
||||
extra := slotLevel - spell.Level
|
||||
if extra < 0 {
|
||||
extra = 0
|
||||
}
|
||||
totalDice := dice + extra
|
||||
supreme := lifeDomainSupremeHealing(c)
|
||||
heal := 0
|
||||
for i := 0; i < totalDice; i++ {
|
||||
if supreme {
|
||||
heal += faces
|
||||
} else {
|
||||
heal += 1 + rand.IntN(faces)
|
||||
}
|
||||
}
|
||||
heal += abilityModifier(c.WIS)
|
||||
heal += lifeDomainHealBonus(c, spell, slotLevel)
|
||||
if heal < 1 {
|
||||
heal = 1
|
||||
}
|
||||
return heal
|
||||
}
|
||||
|
||||
// rollSpellDamageDice rolls a spell's damage dice with cantrip and upcast
|
||||
// scaling. Returns at least 1 if the spell has any dice; 0 if no dice are
|
||||
// listed (utility/buff spells).
|
||||
|
||||
Reference in New Issue
Block a user