Game rules
games/tbs/src/game/README.md on GitHub
This folder holds The Broken Seal's game rules and the tables they read: the party's units and their stats, a new game and the prologue, items and inventories, Psynergy and levelling up, the game flags, enemy units, the party's coins and counters, element levels and classes, battle arithmetic and the Djinn.
See also: Battles, which follows an ability from these tables to its damage, and Shops, which sell the items.
Files
| File | What it holds |
|---|---|
game.c | the guide to the game object: it includes game_internal.h, then the parts in game/ in ROM order |
game_internal.h | the object's includes, and Inventory_CompactSlots, which two parts use |
game/battle_calc.c | damage and success rates: the attack, Psynergy and healing formulas, the element multiplier, the effect rolls and the weapon unleash |
starting_equipment.c | the characters given starting equipment |
*.tsv with the .s beside them | the definition tables (below) |
| Table | One row per | Placed as | Label column |
|---|---|---|---|
items.tsv | item, by item id (324) | gItem_DefinitionTable (item_table.s) | item |
actions.tsv | ability, by ability id (519) | gBattleAction_DefinitionTable (actions.s) | ability |
djinn.tsv | Djinni, 20 to an element: Venus, Mercury, Mars, Jupiter (80) | gDjinn_DefinitionTable (djinn_table.s) | djinni |
enemies.tsv | enemy, by enemy id (165) | gEnemy_Definitions (enemies.s) | enemy |
summons.tsv | summon (16) | summons.s | none |
classes.tsv | class (203) | classes.s | none |
chars.tsv, elempre.tsv, artifact_slots.tsv, level_experience_*.tsv | character, element preset, artifact slot, level | their .s | none |
Each .tsv has a name:type header, one row per entry, and no row numbers:
the first row after the header is entry 0. The build writes it to a
.table beside it, which its .s places with its label. Edit the .tsv.
A column named without a type, such as item, is a label for people: the
build skips it and writes nothing for it. A column named unknown_ or
unk_ is a field whose meaning is not known yet; reserved_ and pad_
name bytes that are zero in every row.
The guide's comment says where to start, and each part's second line says
what it holds: head -2 game/*.c lists them. A part includes the private
header, so it resolves when opened on its own, but it is compiled only through
game.c. The ROM shows the game rules as one object; the parts are ours, cut
where one family ends and the next begins. Link order is in
games/tbs/objects.tsv, from game.
The unit headers are in games/tbs/include/unit/game/. Code in other banks
reaches these functions through the far-call table
games/tbs/src/system/far_call/GAME.S (the ...Far names), so a rename
crosses folders.
Words
- owner and unit: an owner is a number, a unit the
struct BattleUnitrecordUnitGet(owner)returns. 0 to 7 are the playable characters (ISAACtoJENNAand the rest ofcharacter_ids.h), kept in the game state; 0x80 and up are the enemies of the current battle. - party and active owners: the characters travelling with the party,
in order (
gGameState.activeOwners,Party_CountActiveOwners). - inventory entry: a halfword of a unit's 15 inventory slots: the item id
in its low 9 bits (
ITEM_ID_MASK), thenINVENTORY_EQUIPPED,INVENTORY_BROKENand the quantity less one fromINVENTORY_QUANTITY_SHIFT. - ability: a row of
actions.tsv; Psynergy, a Djinni's and a summon's effect, an item's use and an enemy's attack are all abilities. - element: 0 Venus, 1 Mercury, 2 Mars, 3 Jupiter (
ELEMENT_VENUSand the rest), 4 none.Unit_GetElementLevelsgives each element level times ten. - Djinn
groupandindex: a Djinni is its element (0 to 3) and its number within it (0 to 19). A unit holds it, and it is set (on the unit) or on standby, waiting to be set again. - class series: the classes a character's two strongest elements select;
the class is the last in the series whose element levels the character
reaches (
Class_Determine). - flag: one of 4096 bits in
gGameFlagBytes, by number; byte and nibble flags use the same numbering (flags.c). Named flags are inflags.h. unk<offset>: a field whose meaning is not known yet.
Prefixes
| Prefix | What it acts on |
|---|---|
Unit_, UnitGet, UnitCalc, UnitRatio, Owner_ | one unit: stats, HP and PP, Psynergy, levels, elements |
Party_, GameState_, Game_ | the party and the game state |
Item_, Inventory_, PartyInventory_, InventorySnapshot_, ItemCounter_, Equipment_ | items: one unit's inventory, the party's, and the snapshot the Colosso takes |
Character_, Class_, Enemy_, Summon_, SummonDefinition_, BattleAction_ | one entry of a definition table |
GameFlag_ | the game flags |
Djinn_, DjinnRecovery_ | the Djinn, and their standby lists |
Battle_, BattleRandom, BattleTarget_, BattleFx_ | battle arithmetic the battle folder calls |
BattleUnitSet | filling an enemy unit at the start of a battle |
The spellings some sites keep to match, and the comment markers, are in Matching.
Design
A row's place is its id, and its name is the message at the same place
The definition tables have no id column. An item is the row it sits on, and
the same number is its place in three other lists: its name is message
MsgItemName + id, its line on the item screen MsgItemPlainName + id, and
code reaches both by adding the id to the first message's number
(itemId + MsgItemName in the shops and the menus). Abilities, enemies and
Djinn work the same way, with their own first messages. So the tables and the
message catalogs are parallel lists tied only by position: a row inserted in
the middle of items.tsv gives every later item the name of the one before
it, unless the catalogs of all six languages gain a message at the same
place. Rows that no item uses keep their places for the same reason, with
? or = as their names.
Flags are bits that can also be read as bytes
The 4,096 game flags are one array of bits, gGameFlagBytes. A flag
number's low three bits pick the bit and the next nine the byte
(game/flags.c), so eight consecutive flags are also one byte, and the byte
calls (GameFlag_GetByte, GameFlag_SetByte, GameFlag_IncrementByte)
read and write it whole. A small counter can therefore live among the flags,
and it follows the flags' rules: one kept in a scene's temporary range
(0x200 to 0x2ff) is cleared on entry with the rest (see
Scenes), and all of them are saved with the game.
An inventory slot is one halfword
A unit's 15 inventory slots are halfwords: the item id in the low 9 bits,
then the equipped bit (INVENTORY_EQUIPPED, 0x200), the broken bit
(INVENTORY_BROKEN, 0x400), and the quantity less one from bit 11
(INVENTORY_QUANTITY_SHIFT). So an item, its state and its stack move
together in one load and one store, and a slot of 0 is empty, which
Inventory_CompactSlots relies on when it closes the gaps. Code that asks
which item a slot holds masks it with ITEM_ID_MASK first, since the same
item equipped, broken or stacked is a different halfword.
How to find a row
Search the table for the English name in its label column: Healing Ring
in items.tsv, Quake in actions.tsv, Flint in djinn.tsv, Vermin in
enemies.tsv. The row's id is its line number less two (the header is line
1, entry 0 is line 2). The labels are the English edition's names from
games/tbs/text/en.po: an item's name is message MsgItemName + id (386 +
id), an enemy's MsgEnemyName + id (655 + id), an ability's
MsgAbilityName + id (819 + id), and a Djinni's MsgDjinnName + its row
(1119 + row), which is also its ability, 300 + row. A row whose message is
? or = is unused, and the labels keep those marks; a row past the last
name message says (no name message). item_ids.h, ability_ids.h and
djinn_ids.h name the ids the code uses.
How to change an item
An item is its row of items.tsv (struct ItemDefinition in
games/tbs/include/game/item.h):
- Price:
price, in coins. - Who can equip it:
equip_mask, one bit per character, bit 0 for Isaac (character_ids.h). - What it adds to Attack and Defense while equipped:
primary_bonusandsecondary_bonus(UnitCalc,game/units.c). - What it does while equipped: up to four effects,
effect0_kindandeffect0_amounttoeffect3_; the kinds areenum ItemEffectKindinitem.h(ITEM_EFFECT_ADD_HPis 1,ITEM_EFFECT_SCALE_ATTACK11). - What a weapon unleashes:
unleash_action, an ability id, atunleash_chancetimes 5 percent. - What using it does:
action_id, an ability id; see below. - Its name: message
MsgItemName+ id in the PO catalogs (games/tbs/text); the line the item screen shows about it isMsgItemPlainName+ id.
How an item reaches an ability
An item names an ability by its row in actions.tsv, in two columns:
action_idis what the item does when used. In battle (BattleCommand_BuildPlan,../battle/battle/plan.c) the item command plans that ability;use_typesays what the use costs: 1 one is used up, 2 it may break (one time in eight), 4 it leaves an Empty Bottle. Withuse_type3 the ability is instead taught while the item is equipped, as Psynergy (game/levels.c; the Orb of Force and the rest,type6).unleash_actionis what a weapon unleashes in place of an attack (RollWeaponUnleash,game/battle_calc.c).
The ability's row of actions.tsv then gives its element, power, range and
kind; ../battle/README.md follows it to the damage.
Limits
- Item ids are 9 bits (
ITEM_ID_MASK);items.tsvhas 324 rows, of which rows 269 to 323 have no name message of their own. - A unit carries 15 inventory slots (
INVENTORY_SLOTS). - 20 Djinn to an element; 519 abilities; 165 enemies, of which the last has no name message.
- 4096 game flags.
Evidence and open questions
The tables' label columns are the English edition's names from
games/tbs/text/en.po, by the message each row's id reaches; the columns are
named from what the code reads in them; the item effect kinds from what
UnitCalc does with each. item_ids.h, ability_ids.h and djinn_ids.h
name only the ids code uses.
Still unknown:
- XXX: the
unknown_andunk_columns of the tables, and what rows 269 to 323 ofitems.tsvwere for, as they have no name message. - XXX: why game flags 48 to 127 are counted in the save file's summary
(
SaveState_BuildSummaryHeader).
Source
Every file of this folder: src/game.