goldbox.strength

Party strength: the number random encounters are sized from.

PARTYSTRENGTH is ECL opcode $1D, implemented at DUNGEON $1BE8. Twelve of the thirty area scripts call it, and in every one of those the value becomes – after a divide, and sometimes a random reduction – the count operand of LOADMON: literally how many monsters are placed in the fight. The slums use (strength / 3) * 2.

Nothing stores it. The routine walks the eight roster slots on every call and writes only to the ECL variable its operand names, which is why this module recomputes rather than reads a byte. See docs/114-party-strength.md.

The biased fields are used as stored, not as decoded. The THAC0 and armour class fields hold 60 - value, and the routine subtracts its own constant from the byte: 39 from THAC0, 60 from armour class. RosterBlock.thac0 would hand back the number on the character sheet, which is the wrong end of the encoding for arithmetic the game does in the stored space.

No Qt and no transport: from_bytes takes the two blocks a save file or a live read both produce, and read_live takes anything with a read(address, length).

Module Attributes

ROSTER_STATUS

Roster +0x00, the byte the routine tests before it does anything else: zero for an empty slot, bit 7 for a dead one ($01 -> $84 was seen going in on death).

RECORD_HP_MAX

wounds do not shrink a Pool of Radiance encounter, though Curse of the Azure Bonds' version of the same routine reads current and they do there.

THAC0_OFFSET

What the routine subtracts from each biased field.

DIVISOR

the 16-bit sum, divided once at the end.

Functions

from_bytes(save0_bytes, roster_bytes)

From the two blocks the game keeps the party in.

from_saves(save0, save1)

From decoded saves.

read_live(target)

From a running game, through anything with read(address, length).

Classes

Contribution

One roster slot's share of the sum, term by term.

PartyStrength

What PARTYSTRENGTH would return right now, and where it came from.

class goldbox.strength.Contribution[source]

Bases: object

One roster slot’s share of the sum, term by term.

The breakdown is the useful part: a total of 130 says nothing about what to change, and “MALCYON 27” beside “BRUTUS 26” says the wizard is pulling as hard as the fighter.

__init__(slot, name, thac0_term, hp_term, armour_term, cleric_term, magic_user_term, thac0_field=0, armour_field=0, level=0, wrapped=False)
Parameters:
  • slot (int)

  • name (str)

  • thac0_term (int)

  • hp_term (int)

  • armour_term (int)

  • cleric_term (int)

  • magic_user_term (int)

  • thac0_field (int)

  • armour_field (int)

  • level (int)

  • wrapped (bool)

Return type:

None

property armour_class: int
armour_field: int = 0
armour_term: int
cleric_term: int
hp_term: int
level: int = 0
property line: str
magic_user_term: int
name: str
slot: int
property terms: tuple[tuple[str, int], ...]

The named terms that are not zero, in the routine’s own order.

property thac0: int

The number on the character sheet, for display only.

thac0_field: int = 0

The fields as stored, for anyone checking the arithmetic against memory.

thac0_term: int
property total: int
wrapped: bool = False

a current THAC0 worse than 21, which the routine turns into a number near 255 and then multiplies by five.

Type:

The THAC0 subtraction wrapped

goldbox.strength.DIVISOR = 10

the 16-bit sum, divided once at the end. CoAB divides per character instead, so with six characters this is systematically the larger by up to 5.

Type:

$1C51

class goldbox.strength.PartyStrength[source]

Bases: object

What PARTYSTRENGTH would return right now, and where it came from.

__init__(parts)
Parameters:

parts (tuple[goldbox.strength.Contribution, ...])

Return type:

None

property detail: str

The per-character breakdown, one line each, for a tooltip.

parts: tuple[goldbox.strength.Contribution, ...]
property slums_count: int

ECL14’s (s / 3) * 2.

The one scaled count that has been watched end to end, and the reason the number is worth showing at all.

Type:

Monsters in a slums random encounter

property total: int

The 16-bit sum, before the divide.

property value: int
goldbox.strength.RECORD_HP_MAX = 118

wounds do not shrink a Pool of Radiance encounter, though Curse of the Azure Bonds’ version of the same routine reads current and they do there.

Type:

Record offsets. Hit points maximum, not current

goldbox.strength.ROSTER_STATUS = 0

Roster +0x00, the byte the routine tests before it does anything else: zero for an empty slot, bit 7 for a dead one ($01 -> $84 was seen going in on death). $1BF6 BEQ / BMIa dead character stops counting, so a party that loses somebody meets smaller encounters until it raises them.

goldbox.strength.THAC0_OFFSET = 39

What the routine subtracts from each biased field. THAC0’s subtraction at $1C01 has no underflow guard; armour class’s at $1C16 has one (BCC), which is why only the second has a floor here.

goldbox.strength.from_bytes(save0_bytes, roster_bytes)[source]

From the two blocks the game keeps the party in.

save0_bytes is $4900-$64FF – a SAVEDGAME0 payload, or the live read of the same range – and roster_bytes the roster page at $8300. Both are what automap/live.py already reads on every poll.

Parameters:
Return type:

goldbox.strength.PartyStrength

goldbox.strength.from_saves(save0, save1)[source]

From decoded saves. save1 may be a SaveGame1 or the roster bytes.

Parameters:

save0 (goldbox.savegame.SaveGame0)

Return type:

goldbox.strength.PartyStrength

goldbox.strength.read_live(target)[source]

From a running game, through anything with read(address, length).

MemoryTarget in the tests and ViceTarget in the window both qualify, and neither this module nor goldbox/ in general knows which it has.

Return type:

goldbox.strength.PartyStrength