# faction.ludic — a friend/enemy/neutral relationship table (#57 foundation). # # Shared by shooter targeting and NPC AI: "who may hit / hunt / help whom". Each # body carries a `Faction { id }`; a small square matrix records the relationship # between two faction ids. Unset pairs fall back to the classic default — same id # is friendly, different ids are hostile — so a game that never touches the table # still gets sensible enemy/ally behaviour. The matrix is a plain integer buffer # in a module var, so it is deterministic and captured by world_save. property Faction { id: int = 0 } const FAC_MAX: int = 32 const REL_FRIEND: int = 0 const REL_NEUTRAL: int = 1 const REL_HOSTILE: int = 2 # relationships stored as (rel + 1); a zero slot means "unset -> use the default". # A global var initialiser must be a literal, so it is allocated lazily. var fac_tbl: words = null function fac_ensure() -> void { if fac_tbl == null { fac_tbl = words(FAC_MAX * FAC_MAX) } } # Faction.set(a, b, rel) — record a symmetric relationship between two factions. @Namespace(Faction) function faction_set(a: int, b: int, rel: int) -> void { fac_ensure() if (a < 0) or (a >= FAC_MAX) { return } if (b < 0) or (b >= FAC_MAX) { return } fac_tbl[a * FAC_MAX + b] = rel + 1 fac_tbl[b * FAC_MAX + a] = rel + 1 } # Faction.rel(a, b) — the relationship code (0 friend, 1 neutral, 2 hostile). @Namespace(Faction) function faction_rel(a: int, b: int) -> int { fac_ensure() if (a >= 0) and (a < FAC_MAX) and (b >= 0) and (b < FAC_MAX) { let v = fac_tbl[a * FAC_MAX + b] if v != 0 { return v - 1 } } if a == b { return REL_FRIEND } return REL_HOSTILE } # Faction.hostile(a, b) / Faction.friendly(a, b) — the common yes/no queries. @Namespace(Faction) function faction_hostile(a: int, b: int) -> int { if faction_rel(a, b) == REL_HOSTILE { return 1 } return 0 } @Namespace(Faction) function faction_friendly(a: int, b: int) -> int { if faction_rel(a, b) == REL_FRIEND { return 1 } return 0 } # Faction.id_of(e) — the faction id an entity carries (-1 if it has no Faction). @Namespace(Faction) function faction_id_of(e: int) -> int { let P = World.prop_id("Faction") if P < 0 { return 0 - 1 } if World.has(e, P) == 0 { return 0 - 1 } let f = World.field_id(P, "id") if f < 0 { return 0 - 1 } return World.get(e, P, f) } # Faction.enemies(a, b) — true when the two entities' factions are hostile; the # one-call test the shooter and AI reach for. Missing factions read as hostile # (an unaligned hazard hits anyone) unless the ids happen to match. @Namespace(Faction) function faction_enemies(ea: int, eb: int) -> int { return faction_hostile(faction_id_of(ea), faction_id_of(eb)) }