mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
N3/P6c: a fight the whole party sits down for
`!fight` seats the expedition's roster instead of the one player who typed it. Seat 0 is the leader, always: the session row is theirs, the lock is theirs, and `!flee`, the fork, and `!extract` stay their call. A monster that wins initiative now swings before anyone speaks. The session layer used to park every new fight on a player_turn, which is true of the hardcoded solo order and a lie about a party's -- the enemy would forfeit round 1 and nobody would notice. `startPartyCombatSession` rolls the order and sets the phase from it; `handleFightCmd` settles the round before it announces, so the opening block narrates the hit rather than quietly showing its damage. Members were invisible to two commands that had no business ignoring them: `!cast` queued a spell for "next combat" while its caster was standing in one, and `!rest` healed a seated member to full mid boss fight. Both now resolve through the party. Nobody leaves without an answer. A downed member's `!fight` opens the party's fight and tells them why they are not in it. The leader's `!extract` reaches everyone it drags out of the dungeon, and everyone rolls for what moved into their house while they were gone. Supplies burn at 50% x N x 4/5 -- a party eats more than one and less than N. The ratio is exact: 0.8 as a float truncates a party of three to 119%, a permanent tax nobody would have found. Solo is untouched, byte for byte. One seat means one build, one INSERT, no participant rows, the same RNG draws in the same order -- the combat characterization golden does not move, and neither does the balance corpus.
This commit is contained in:
@@ -462,11 +462,12 @@ func startCombatSession(userID id.UserID, runID, encounterID, enemyID string, pl
|
||||
// roster_size bump, and the single unwrapped INSERT the solo path has always
|
||||
// issued. That is the invariant the whole balance corpus rests on.
|
||||
func (p *AdventurePlugin) startPartyCombatSession(
|
||||
runID, encounterID, enemyID string, enemyHP int, seats []CombatSeatSetup,
|
||||
runID, encounterID, enemyID string, enemy *Combatant, seats []CombatSeatSetup,
|
||||
) (*CombatSession, error) {
|
||||
if len(seats) == 0 {
|
||||
return nil, fmt.Errorf("start combat session: empty roster")
|
||||
}
|
||||
enemyHP := enemy.Stats.MaxHP
|
||||
owner := seats[0]
|
||||
sess, err := startCombatSession(owner.UserID, runID, encounterID, enemyID,
|
||||
owner.HP, owner.HPMax, enemyHP, enemyHP)
|
||||
@@ -492,6 +493,17 @@ func (p *AdventurePlugin) startPartyCombatSession(
|
||||
}
|
||||
sess.Participants = ps
|
||||
sess.rosterSize = len(seats)
|
||||
|
||||
// startCombatSession parks every fight on a player_turn, because a solo
|
||||
// order is hardcoded [player, enemy] and the player always leads. A party
|
||||
// rolls for initiative and the monster can win it, so round 1's phase has
|
||||
// to come from the order rather than from that assumption — otherwise the
|
||||
// cursor snaps forward to the first player slot on resume and the enemy
|
||||
// silently forfeits its opening turn. Round 2+ already derives this in
|
||||
// stepRoundEnd.
|
||||
order := turnOrder(sess, sess.Round, seatCombatants(seats), enemy)
|
||||
sess.Phase = phaseForSeat(order[0])
|
||||
sess.Statuses.TurnIdx = 0
|
||||
dirty = true
|
||||
}
|
||||
|
||||
@@ -504,12 +516,15 @@ func (p *AdventurePlugin) startPartyCombatSession(
|
||||
}
|
||||
|
||||
// CombatSeatSetup is one character's entry into a fight: who they are, the HP
|
||||
// pool they bring, and the modifiers their fight-start one-shots are read off.
|
||||
// pool they bring, the modifiers their fight-start one-shots are read off, and
|
||||
// the built combatant itself — which the initiative roll needs, and which the
|
||||
// caller threads on to the opening block rather than rebuilding the roster.
|
||||
type CombatSeatSetup struct {
|
||||
UserID id.UserID
|
||||
HP int
|
||||
HPMax int
|
||||
Mods CombatModifiers
|
||||
C *Combatant
|
||||
}
|
||||
|
||||
// getActiveCombatSession returns the player's in-flight fight, or (nil, nil).
|
||||
|
||||
Reference in New Issue
Block a user