# 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 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" # Faction.set(a, b, rel) — record a symmetric relationship between two factions. @Namespace(Faction) function faction_set(gameplay_st: mut GameplayState, a: int, b: int, rel: int) -> void { if (a < 0) or (a >= FAC_MAX) { return } if (b < 0) or (b >= FAC_MAX) { return } gameplay_st.fac_tbl[a * FAC_MAX + b] = rel + 1 gameplay_st.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(gameplay_st: mut GameplayState, a: int, b: int) -> int { if (a >= 0) and (a < FAC_MAX) and (b >= 0) and (b < FAC_MAX) { let v = gameplay_st.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(gameplay_st: mut GameplayState, a: int, b: int) -> int { if faction_rel(gameplay_st, a, b) == REL_HOSTILE { return 1 } return 0 } @Namespace(Faction) function faction_friendly(gameplay_st: mut GameplayState, a: int, b: int) -> int { if faction_rel(gameplay_st, 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 -1 } if World.has(e, P) == 0 { return -1 } let f = World.field_id(P, "id") if f < 0 { return -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(gameplay_st: mut GameplayState, ea: int, eb: int) -> int { return faction_hostile(gameplay_st, faction_id_of(ea), faction_id_of(eb)) }