ludic/packages/ludic.gameplay/faction.ludic

61 lines
2.6 KiB
Text

# 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))
}