refactor(packages): 0.R5 - the rest of the lazy starts are defaults - hints' rail (sized by hints_config), lab's CRC table, rpg's crafting / items / inventory / quests / dialog, shooter's weapons, prefs, and npc's and gameplay's empty ensures gone; gameplay's faction table sits beside its state, since a default names only what is declared before it; --tighten over their tests and examples

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-27 03:07:29 +03:00
parent 9e4f4edb92
commit f516148e46
15 changed files with 73 additions and 169 deletions

View file

@ -23,10 +23,14 @@ event Died { e: int = 0, src: int = 0 }
event Healed { e: int = 0, amount: int = 0 }
event Crit { target: int = 0, src: int = 0 } # the attacker's crit_pct fired
# the faction table: FAC_MAX ids square, every pair unset (faction.ludic)
const FAC_MAX: int = 32
function fac_table() -> words { return words(FAC_MAX * FAC_MAX) }
state GameplayState {
combat_reflecting: bool = false # thorns never reflect thorns
combat_pending: int = 0
fac_tbl: words = words(FAC_MAX * FAC_MAX)
fac_tbl: words = fac_table()
}
# the mutable channel for lever 6: listeners rewrite the pending damage here.

View file

@ -9,20 +9,14 @@
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.
function fac_ensure(gameplay_st: mut GameplayState) -> void {
}
# 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 {
fac_ensure(gameplay_st)
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
@ -31,7 +25,6 @@ function fac_ensure(gameplay_st: mut GameplayState) -> void {
# 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 {
fac_ensure(gameplay_st)
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 }

View file

@ -5,7 +5,7 @@ export state HintsState {
hn_foreign: []string = new []string # muted keys with no card in this build, written back as read
hn_shown: int = 3 # how many the rail holds
hn_every: float = 1.0 # seconds between passes
hn_top: []int = null # the cards on the rail this pass
hn_top: []int = hn_slots(3) # the cards on the rail this pass
hn_n: int = 0
hn_t: float = 0.0 # seconds to the next pass
hn_pick: int = 0 # which chip the key would open
@ -28,21 +28,19 @@ function hn_fact(hints_st: HintsState, what: int, card: int) -> void {
export function hints_config(hints_st: mut HintsState, shown: int, every: float) -> void {
hints_st.hn_shown = shown
hints_st.hn_every = every
hints_st.hn_top = null
hints_st.hn_top = hn_slots(shown)
}
function hn_ensure(hints_st: mut HintsState) -> void {
if hints_st.hn_top == null or len(hints_st.hn_top) != hints_st.hn_shown {
hints_st.hn_top = new []int
for i in 0 .. hints_st.hn_shown { push(hints_st.hn_top, -1) }
}
# a rail of n empty places
function hn_slots(n: int) -> []int {
let out = new []int
for i in 0 .. n { push(out, -1) }
return out
}
# the next pass comes on the next tick rather than in a second
export function hints_refresh(hints_st: mut HintsState) -> void { hints_st.hn_t = 0.0 }
export function hints_tick(hints_st: mut HintsState, dt: float) -> void {
hn_ensure(hints_st)
hints_st.hn_t = hints_st.hn_t - dt
if hints_st.hn_t > 0.0 { return }
hints_st.hn_t = hints_st.hn_every
@ -52,7 +50,6 @@ export function hints_tick(hints_st: mut HintsState, dt: float) -> void {
# one pass now: the rail re-sorted, the pick kept in range, and the card following its subject
# along the rail - or put down when what it was about stopped being true
export function hints_sort(hints_st: mut HintsState) -> void {
hn_ensure(hints_st)
hints_st.hn_n = 0
let on = HintsWorld.showing()
for u in 0 .. 4 {
@ -75,8 +72,7 @@ export function hints_sort(hints_st: mut HintsState) -> void {
}
# the cards on the rail, most urgent first (empty while the rail is not showing)
export function hints_rail(hints_st: mut HintsState) -> []int {
hn_ensure(hints_st)
export function hints_rail(hints_st: HintsState) -> []int {
let out = new []int
if not HintsWorld.showing() { return out }
for k in 0 .. hints_st.hn_n { push(out, hints_st.hn_top[k]) }
@ -95,7 +91,7 @@ export function hints_pick(hints_st: HintsState) -> int { return hints_st.hn_pic
export function hints_hot(hints_st: HintsState, k: int) -> bool { return k == hints_st.hn_pick }
export function hints_reset(hints_st: mut HintsState) -> void {
hints_st.hn_top = null
hints_st.hn_top = hn_slots(hints_st.hn_shown)
hints_st.hn_n = 0
hints_st.hn_t = 0.0
hints_st.hn_pick = 0

View file

@ -2,21 +2,21 @@
# compressed (a PNG anything opens, with no zlib here): the shot is the PPM's pixels, filter 0
# on every row, in deflate's stored blocks, with its CRCs and its Adler-32.
function lp_table_init(lab_st: mut LabState) -> void {
if lab_st.lp_table != null { return }
lab_st.lp_table = new []long
# the CRC-32 table, made once with the state
function lp_crc_table() -> []long {
let t = new []long
let poly: long = 3988292384 # 0xEDB88320: eight hex digits would be a 32-bit pattern
for n in 0 .. 256 {
var c = long(n)
for k in 0 .. 8 {
if (c & long(1)) != long(0) { c = (c >> long(1)) ^ poly } else { c = c >> long(1) }
}
push(lab_st.lp_table, c)
push(t, c)
}
return t
}
# CRC-32 of out[from .. from + n)
function lp_crc(lab_st: mut LabState, out: []byte, from: int, n: int) -> long {
lp_table_init(lab_st)
let mask: long = 4294967295
var c = mask
for i in from .. from + n {

View file

@ -2,7 +2,7 @@
export state LabState {
lb_plate: Actor = null
lb_plate_dir: string = ""
lp_table: []long = null
lp_table: []long = lp_crc_table()
lp_w: int = 0
lp_h: int = 0
lb_scene: string = ""

View file

@ -1,7 +1,6 @@
# census.ludic - making one (a slot reused before a new one, a look, a name, a start on its own
# ground out of the players' sight where that can be had), a post's fixed person, and leaving
export function npc_spawn(npc_st: mut NpcState, kind: int) -> NpcPerson {
np_ensure()
if npc_st.np_n >= npc_st.np_max or kind < 0 or kind >= NPCK_COUNT { return null }
var p: NpcPerson = null
for i in 0 .. len(npc_st.np_walkers) {
@ -94,7 +93,6 @@ export function npc_set_at(p: NpcPerson, x: float, z: float) -> void {
# somebody fixed at a post (a vendor behind the counter): never walks, never counted, never leaves,
# turns to a player within its kind's notice and back to `yaw` when they go
export function npc_place(npc_st: mut NpcState, kind: int, x: float, z: float, yaw: float, name: string, look: int) -> NpcPerson {
np_ensure()
let p = new NpcPerson
p.slot = len(npc_st.np_posts)
np_fresh(p, kind, look)

View file

@ -3,7 +3,6 @@
# on: this machine's own walkers leave, and only the host's are drawn until it is turned off
export function npc_mirror(npc_st: mut NpcState, on: bool) -> void {
np_ensure()
if npc_st.np_mirror == on { return }
npc_st.np_mirror = on
for i in 0 .. len(npc_st.np_walkers) { npc_retire(npc_st, npc_st.np_walkers[i]) }
@ -14,7 +13,6 @@ export function npc_mirroring(npc_st: NpcState) -> bool { return npc_st.np_mirro
# the host's slot: somebody new in it (born), or nobody (gone)
export function npc_put(npc_st: mut NpcState, slot: int, id: int, kind: int, look: int, seed: float, name: string, hx: float, hz: float) -> NpcPerson {
np_ensure()
if slot < 0 or slot >= npc_st.np_max or kind < 0 or kind >= NPCK_COUNT { return null }
while len(npc_st.np_walkers) <= slot {
let q = new NpcPerson
@ -60,7 +58,6 @@ export function npc_heard(npc_st: NpcState, slot: int, x: float, y: float, z: fl
export function npc_heard_lately(npc_st: NpcState, p: NpcPerson) -> bool { return p.heard and npc_st.np_clock - p.heard_t <= 6.0 }
export function npc_mirror_tick(npc_st: mut NpcState, dt: float) -> void {
np_ensure()
npc_st.np_clock = npc_st.np_clock + dt
let k = Math.min(1.0, dt * 8.0)
for i in 0 .. len(npc_st.np_walkers) {

View file

@ -39,37 +39,30 @@ function np_fact(npc_st: NpcState, what: int, p: NpcPerson, player: int) -> NpcF
return f
}
function np_ensure() -> void {
}
# how many walkers the place can hold at once
export function npc_config(npc_st: mut NpcState, max: int) -> void { npc_st.np_max = max }
export function npc_seed(npc_st: NpcState, n: int) -> void {
np_ensure()
rng_seed(npc_st.np_dice, n)
}
# 0 .. 1, and whole seconds lo .. hi, off the package's dice
export function npc_rnd(npc_st: NpcState) -> float {
np_ensure()
return rng_float(npc_st.np_dice)
}
export function npc_between(npc_st: NpcState, lo: int, hi: int) -> int {
np_ensure()
return rng_between(npc_st.np_dice, lo, hi)
}
export function npc_secs(npc_st: NpcState, lo: int, hi: int) -> float { return float(npc_between(npc_st, lo, np_hi(lo, hi))) }
export function npc_walkers(npc_st: NpcState) -> []NpcPerson {
np_ensure()
return npc_st.np_walkers
}
export function npc_posts(npc_st: NpcState) -> []NpcPerson {
np_ensure()
return npc_st.np_posts
}
@ -79,19 +72,16 @@ export function npc_max(npc_st: NpcState) -> int { return npc_st.np_max }
# a walker's slot or a post, and anyone by id
export function npc_at(npc_st: NpcState, slot: int) -> NpcPerson {
np_ensure()
if slot < 0 or slot >= len(npc_st.np_walkers) { return null }
return npc_st.np_walkers[slot]
}
export function npc_post(npc_st: NpcState, i: int) -> NpcPerson {
np_ensure()
if i < 0 or i >= len(npc_st.np_posts) { return null }
return npc_st.np_posts[i]
}
export function npc_by_id(npc_st: NpcState, id: int) -> NpcPerson {
np_ensure()
for i in 0 .. len(npc_st.np_walkers) { if npc_st.np_walkers[i].active and npc_st.np_walkers[i].id == id { return npc_st.np_walkers[i] } }
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active and npc_st.np_posts[i].id == id { return npc_st.np_posts[i] } }
return null

View file

@ -1,7 +1,6 @@
# tick.ludic - a step of everyone, on the machine that runs the place: the census on the half hour,
# the day's end (only ever out of every player's sight), the walk or the act, and who noticed whom
export function npc_tick(npc_st: mut NpcState, dt: float) -> void {
np_ensure()
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(npc_st, npc_st.np_posts[i], dt) } }
if npc_st.np_hold or npc_st.np_mirror { return }
np_census(npc_st)
@ -15,7 +14,6 @@ export function npc_tick(npc_st: mut NpcState, dt: float) -> void {
# the posts alone: a machine that does not run the place still turns its vendors to whoever comes
export function npc_posts_tick(npc_st: mut NpcState, dt: float) -> void {
np_ensure()
for i in 0 .. len(npc_st.np_posts) { if npc_st.np_posts[i].active { np_step_post(npc_st, npc_st.np_posts[i], dt) } }
}
@ -82,7 +80,6 @@ export function npc_hold(npc_st: mut NpcState, on: bool) -> void { npc_st.np_hol
# everyone gone, as a new trip starts; the posts are the place's and stay
export function npc_reset(npc_st: mut NpcState) -> void {
np_ensure()
for i in 0 .. len(npc_st.np_walkers) { npc_retire(npc_st, npc_st.np_walkers[i]) }
npc_st.np_n = 0
npc_st.np_next_h = 0.0

View file

@ -11,18 +11,12 @@ const PREFS_MAX: int = 64
state PrefsState {
prefs_path: pointer = null
prefs_names: pointers = null
prefs_vals: words = null
prefs_names: pointers = pointers(PREFS_MAX)
prefs_vals: words = words(PREFS_MAX)
prefs_n: int = 0
}
namespace Prefs {
internal function init(prefs_st: mut PrefsState) -> void {
if prefs_st.prefs_names != null { return }
prefs_st.prefs_names = pointers(PREFS_MAX)
prefs_st.prefs_vals = words(PREFS_MAX)
prefs_st.prefs_n = 0
}
internal function find(prefs_st: PrefsState, key: pointer) -> int {
var i = 0
@ -31,7 +25,6 @@ namespace Prefs {
}
export function get(prefs_st: mut PrefsState, key: pointer, fallback: int) -> int {
init(prefs_st)
let i = find(prefs_st, key)
if i < 0 { return fallback }
return prefs_st.prefs_vals[i]
@ -45,7 +38,6 @@ namespace Prefs {
}
export function set(prefs_st: mut PrefsState, key: pointer, value: int) -> void {
init(prefs_st)
var i = find(prefs_st, key)
if i < 0 {
if prefs_st.prefs_n >= PREFS_MAX { return }
@ -89,7 +81,6 @@ namespace Prefs {
}
export function load(prefs_st: mut PrefsState, path: pointer) -> void {
init(prefs_st)
prefs_st.prefs_path = path
let buf = read_all(path)
if buf == null { return }
@ -109,7 +100,6 @@ namespace Prefs {
}
export function save(prefs_st: mut PrefsState) -> void {
init(prefs_st)
if prefs_st.prefs_path == null { return }
let f = file_open(prefs_st.prefs_path, "wb")
if f == null { return }

View file

@ -10,20 +10,12 @@ const CRAFT_MAX: int = 128
const CRAFT_ING_MAX: int = 512
function craft_ensure(rpg_st: mut RpgState) -> void {
if rpg_st.recipe_reg == null {
rpg_st.recipe_reg = Dict.new()
rpg_st.recipe_out = pointers(CRAFT_MAX); rpg_st.recipe_outqty = words(CRAFT_MAX)
rpg_st.ing_rid = words(CRAFT_ING_MAX); rpg_st.ing_name = pointers(CRAFT_ING_MAX); rpg_st.ing_qty = words(CRAFT_ING_MAX)
}
}
event cancellable CraftRequested { e: int = 0, recipe: int = 0 }
event CraftCompleted { e: int = 0, recipe: int = 0 }
# Craft.recipe(name, out_item, out_qty) — declare a recipe; returns its id.
@Namespace(Craft) function craft_recipe(rpg_st: mut RpgState, name: pointer, out_item: pointer, out_qty: int) -> int {
craft_ensure(rpg_st)
if rpg_st.recipe_n >= CRAFT_MAX { return -1 }
let id = rpg_st.recipe_n
rpg_st.recipe_out[id] = out_item; rpg_st.recipe_outqty[id] = out_qty
@ -31,10 +23,9 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
Dict.set(rpg_st.recipe_reg, name, id)
return id
}
@Namespace(Craft) function craft_id(rpg_st: mut RpgState, name: pointer) -> int { craft_ensure(rpg_st); return Dict.get_or(rpg_st.recipe_reg, name, -1) }
@Namespace(Craft) function craft_id(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.recipe_reg, name, -1) }
# Craft.ingredient(recipe_name, item, qty) — add an ingredient requirement.
@Namespace(Craft) function craft_ingredient(rpg_st: mut RpgState, recipe_name: pointer, item: pointer, qty: int) -> void {
craft_ensure(rpg_st)
let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return }
if rpg_st.ing_n >= CRAFT_ING_MAX { return }
@ -44,7 +35,6 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
# Craft.can(e, recipe_name) — does the owner hold every ingredient?
@Namespace(Craft) function craft_can(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int {
craft_ensure(rpg_st)
let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return 0 }
var i = 0
@ -59,7 +49,6 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
# Craft.make(e, recipe_name) — consume ingredients + produce output; 1 on success.
@Namespace(Craft) function craft_make(rpg_st: mut RpgState, e: int, recipe_name: pointer) -> int {
craft_ensure(rpg_st)
let rid = craft_id(rpg_st, recipe_name)
if rid < 0 { return 0 }
if craft_can(rpg_st, e, recipe_name) == 0 { return 0 }

View file

@ -9,14 +9,6 @@ const DLG_NODE_MAX: int = 512
const DLG_CHOICE_MAX: int = 1024
function dlg_ensure(rpg_st: mut RpgState) -> void {
if rpg_st.dlg_reg == null {
rpg_st.dlg_reg = Dict.new()
rpg_st.dlg_tree_start = words(DLG_TREE_MAX)
rpg_st.dlg_node_tree = words(DLG_NODE_MAX); rpg_st.dlg_node_text = pointers(DLG_NODE_MAX)
rpg_st.dlg_choice_node = words(DLG_CHOICE_MAX); rpg_st.dlg_choice_label = pointers(DLG_CHOICE_MAX); rpg_st.dlg_choice_next = words(DLG_CHOICE_MAX)
}
}
property Dialog { tree: int = 0, node: int = 0, active: int = 0 }
@ -27,7 +19,6 @@ event DialogEnded { e: int = 0 }
# Dialog.tree(name) — register (or fetch) a tree id.
@Namespace(Dialog) function dialog_tree(rpg_st: mut RpgState, name: pointer) -> int {
dlg_ensure(rpg_st)
let ex = Dict.get_or(rpg_st.dlg_reg, name, -1)
if ex >= 0 { return ex }
if rpg_st.dlg_tree_n >= DLG_TREE_MAX { return -1 }
@ -40,7 +31,6 @@ event DialogEnded { e: int = 0 }
# Dialog.node(tree_name, text) — add a line node; the first node added to a tree is
# its entry point. Returns the node id.
@Namespace(Dialog) function dialog_node(rpg_st: mut RpgState, tree_name: pointer, text: pointer) -> int {
dlg_ensure(rpg_st)
let tid = dialog_tree(rpg_st, tree_name)
if tid < 0 { return -1 }
if rpg_st.dlg_node_n >= DLG_NODE_MAX { return -1 }
@ -52,7 +42,6 @@ event DialogEnded { e: int = 0 }
}
# Dialog.choice(node, label, next_node) — add a branch (next_node -1 ends).
@Namespace(Dialog) function dialog_choice(rpg_st: mut RpgState, node: int, label: pointer, next_node: int) -> int {
dlg_ensure(rpg_st)
if rpg_st.dlg_choice_n >= DLG_CHOICE_MAX { return -1 }
let id = rpg_st.dlg_choice_n
rpg_st.dlg_choice_node[id] = node; rpg_st.dlg_choice_label[id] = label; rpg_st.dlg_choice_next[id] = next_node
@ -71,7 +60,6 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
# Dialog.start(e, tree_name) — open the tree on a speaker (DialogStarted + Line).
@Namespace(Dialog) function dialog_start(rpg_st: mut RpgState, e: int, tree_name: pointer) -> void {
dlg_ensure(rpg_st)
let tid = dialog_tree(rpg_st, tree_name)
if tid < 0 { return }
dlg_set(e, "tree", tid)
@ -87,7 +75,6 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
}
# Dialog.count(e) — number of choices at the current node.
@Namespace(Dialog) function dialog_count(rpg_st: mut RpgState, e: int) -> int {
dlg_ensure(rpg_st)
if dlg_get(e, "active") == 0 { return 0 }
let node = dlg_get(e, "node")
var n = 0; var i = 0
@ -96,7 +83,6 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
}
# Dialog.choose(e, i) — take the i-th choice (veto-able); advance or end.
@Namespace(Dialog) function dialog_choose(rpg_st: mut RpgState, e: int, idx: int) -> int {
dlg_ensure(rpg_st)
if dlg_get(e, "active") == 0 { return 0 }
let node = dlg_get(e, "node")
var seen = 0; var i = 0; var pick = -1

View file

@ -11,52 +11,45 @@ const INV_MAX: int = 256
# ---- item registry --------------------------------------------------------
state RpgState {
recipe_reg: Dict = null # recipe name -> id
recipe_reg: Dict = Dict.new() # recipe name -> id
recipe_n: int = 0
recipe_out: pointers = null # output item name
recipe_outqty: words = null
recipe_out: pointers = pointers(CRAFT_MAX) # output item name
recipe_outqty: words = words(CRAFT_MAX)
ing_n: int = 0
ing_rid: words = null # owning recipe id
ing_name: pointers = null # ingredient item name
ing_qty: words = null
dlg_reg: Dict = null # tree name -> tree id
ing_rid: words = words(CRAFT_ING_MAX) # owning recipe id
ing_name: pointers = pointers(CRAFT_ING_MAX) # ingredient item name
ing_qty: words = words(CRAFT_ING_MAX)
dlg_reg: Dict = Dict.new() # tree name -> tree id
dlg_tree_n: int = 0
dlg_tree_start: words = null # first node id per tree
dlg_tree_start: words = words(DLG_TREE_MAX) # first node id per tree
dlg_node_n: int = 0
dlg_node_tree: words = null
dlg_node_text: pointers = null
dlg_node_tree: words = words(DLG_NODE_MAX)
dlg_node_text: pointers = pointers(DLG_NODE_MAX)
dlg_choice_n: int = 0
dlg_choice_node: words = null # owning node
dlg_choice_label: pointers = null
dlg_choice_next: words = null # next node id, or -1 to end
item_reg: Dict = null
dlg_choice_node: words = words(DLG_CHOICE_MAX) # owning node
dlg_choice_label: pointers = pointers(DLG_CHOICE_MAX)
dlg_choice_next: words = words(DLG_CHOICE_MAX) # next node id, or -1 to end
item_reg: Dict = Dict.new()
item_n: int = 0
item_stackmax: words = null
item_weight: words = null
item_value: words = null
inv_counts: Dict = null
quest_reg: Dict = null # quest name -> id
item_stackmax: words = words(INV_MAX)
item_weight: words = words(INV_MAX)
item_value: words = words(INV_MAX)
inv_counts: Dict = Dict.new()
quest_reg: Dict = Dict.new() # quest name -> id
quest_n: int = 0
quest_state: words = null
quest_state: words = words(QUEST_MAX)
obj_n: int = 0
obj_qid: words = null # owning quest id
obj_kind: words = null
obj_target: words = null # a target id/tag (kind-specific; 0 = any)
obj_need: words = null
obj_have: words = null
quest_flags: Dict = null # global flag store
obj_qid: words = words(OBJ_MAX) # owning quest id
obj_kind: words = words(OBJ_MAX)
obj_target: words = words(OBJ_MAX) # a target id/tag (kind-specific; 0 = any)
obj_need: words = words(OBJ_MAX)
obj_have: words = words(OBJ_MAX)
quest_flags: Dict = Dict.new() # global flag store
}
function item_ensure(rpg_st: mut RpgState) -> void {
if rpg_st.item_reg == null {
rpg_st.item_reg = Dict.new()
rpg_st.item_stackmax = words(INV_MAX); rpg_st.item_weight = words(INV_MAX); rpg_st.item_value = words(INV_MAX)
}
}
# Item.def(name, stack_max, weight, value) — register an item template. Returns id.
@Namespace(Item) function item_def(rpg_st: mut RpgState, name: pointer, stack_max: int, weight: int, value: int) -> int {
item_ensure(rpg_st)
if rpg_st.item_n >= INV_MAX { return -1 }
let id = rpg_st.item_n
rpg_st.item_stackmax[id] = stack_max; rpg_st.item_weight[id] = weight; rpg_st.item_value[id] = value
@ -64,7 +57,7 @@ function item_ensure(rpg_st: mut RpgState) -> void {
Dict.set(rpg_st.item_reg, name, id)
return id
}
@Namespace(Item) function item_id(rpg_st: mut RpgState, name: pointer) -> int { item_ensure(rpg_st); return Dict.get_or(rpg_st.item_reg, name, -1) }
@Namespace(Item) function item_id(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.item_reg, name, -1) }
@Namespace(Item) function item_value_of(rpg_st: mut RpgState, name: pointer) -> int {
let id = item_id(rpg_st, name); if id < 0 { return 0 }
return rpg_st.item_value[id]
@ -73,7 +66,6 @@ function item_ensure(rpg_st: mut RpgState) -> void {
# ---- per-owner counts -----------------------------------------------------
property Inventory { gold: int = 0, weight_max: int = 0, slots: int = 0 }
function inv_ensure(rpg_st: mut RpgState) -> void { if rpg_st.inv_counts == null { rpg_st.inv_counts = Dict.new() } }
function inv_key(e: int, name: pointer) -> pointer { return `{e}/{name}` }
event ItemAdded { e: int = 0, item: int = 0, qty: int = 0 } # item = registry id
@ -82,14 +74,12 @@ event cancellable ItemUse { e: int = 0, item: int = 0 } # veto a use (
# Inventory.add(e, name, n) — add n of an item; emits ItemAdded.
@Namespace(Inventory) function inventory_add(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> void {
inv_ensure(rpg_st)
let k = inv_key(e, name)
Dict.set(rpg_st.inv_counts, k, Dict.get_or(rpg_st.inv_counts, k, 0) + n)
emit ItemAdded(e: e, item: item_id(rpg_st, name), qty: n)
}
# Inventory.count(e, name) — how many the owner holds.
@Namespace(Inventory) function inventory_count(rpg_st: mut RpgState, e: int, name: pointer) -> int {
inv_ensure(rpg_st)
return Dict.get_or(rpg_st.inv_counts, inv_key(e, name), 0)
}
@Namespace(Inventory) function inventory_has(rpg_st: mut RpgState, e: int, name: pointer) -> int {
@ -98,7 +88,6 @@ event cancellable ItemUse { e: int = 0, item: int = 0 } # veto a use (
}
# Inventory.remove(e, name, n) — remove up to n; returns 1 if it had enough.
@Namespace(Inventory) function inventory_remove(rpg_st: mut RpgState, e: int, name: pointer, n: int) -> int {
inv_ensure(rpg_st)
let k = inv_key(e, name)
let have = Dict.get_or(rpg_st.inv_counts, k, 0)
if have < n { return 0 }

View file

@ -15,14 +15,6 @@ const QUEST_MAX: int = 128
const OBJ_MAX: int = 512
function quest_ensure(rpg_st: mut RpgState) -> void {
if rpg_st.quest_reg == null {
rpg_st.quest_reg = Dict.new(); rpg_st.quest_flags = Dict.new()
rpg_st.quest_state = words(QUEST_MAX)
rpg_st.obj_qid = words(OBJ_MAX); rpg_st.obj_kind = words(OBJ_MAX); rpg_st.obj_target = words(OBJ_MAX)
rpg_st.obj_need = words(OBJ_MAX); rpg_st.obj_have = words(OBJ_MAX)
}
}
event QuestStarted { quest: int = 0 }
event QuestCompleted { quest: int = 0 }
@ -30,7 +22,6 @@ event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, n
# Quest.define(name) — register a quest (state hidden). Returns id.
@Namespace(Quest) function quest_define(rpg_st: mut RpgState, name: pointer) -> int {
quest_ensure(rpg_st)
if rpg_st.quest_n >= QUEST_MAX { return -1 }
let id = rpg_st.quest_n
rpg_st.quest_state[id] = 0
@ -38,10 +29,9 @@ event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, n
Dict.set(rpg_st.quest_reg, name, id)
return id
}
@Namespace(Quest) function quest_id(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_reg, name, -1) }
@Namespace(Quest) function quest_id(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.quest_reg, name, -1) }
# Quest.objective(quest_name, kind, target, need) — add an objective.
@Namespace(Quest) function quest_objective(rpg_st: mut RpgState, quest_name: pointer, kind: int, target: int, need: int) -> int {
quest_ensure(rpg_st)
let qid = quest_id(rpg_st, quest_name)
if qid < 0 { return -1 }
if rpg_st.obj_n >= OBJ_MAX { return -1 }
@ -53,14 +43,12 @@ event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, n
}
# Quest.start(name) — activate a quest (emits QuestStarted).
@Namespace(Quest) function quest_start(rpg_st: mut RpgState, name: pointer) -> void {
quest_ensure(rpg_st)
let qid = quest_id(rpg_st, name)
if qid < 0 { return }
rpg_st.quest_state[qid] = 2
emit QuestStarted(quest: qid)
}
@Namespace(Quest) function quest_state_of(rpg_st: mut RpgState, name: pointer) -> int {
quest_ensure(rpg_st)
let qid = quest_id(rpg_st, name)
if qid < 0 { return -1 }
return rpg_st.quest_state[qid]
@ -80,7 +68,6 @@ function quest_all_done(rpg_st: RpgState, qid: int) -> int {
# quest's matching objectives tick up (target 0 on the objective = "any"); a quest
# whose objectives are all met auto-completes.
@Namespace(Quest) function quest_notify(rpg_st: mut RpgState, kind: int, target: int, amount: int) -> void {
quest_ensure(rpg_st)
var i = 0
while i < rpg_st.obj_n {
let qid = rpg_st.obj_qid[i]
@ -104,5 +91,5 @@ function quest_all_done(rpg_st: RpgState, qid: int) -> int {
}
# ---- global flag store (branching / world state) --------------------------
@Namespace(Quest) function quest_flag(rpg_st: mut RpgState, name: pointer, value: int) -> void { quest_ensure(rpg_st); Dict.set(rpg_st.quest_flags, name, value) }
@Namespace(Quest) function quest_flag_get(rpg_st: mut RpgState, name: pointer) -> int { quest_ensure(rpg_st); return Dict.get_or(rpg_st.quest_flags, name, 0) }
@Namespace(Quest) function quest_flag(rpg_st: mut RpgState, name: pointer, value: int) -> void { Dict.set(rpg_st.quest_flags, name, value) }
@Namespace(Quest) function quest_flag_get(rpg_st: mut RpgState, name: pointer) -> int { return Dict.get_or(rpg_st.quest_flags, name, 0) }

View file

@ -70,36 +70,26 @@ event WeaponFired { owner: int = 0, def_id: int = 0 }
# Weapon registry (name-keyed, mod-friendly — add a gun with no code)
# ---------------------------------------------------------------------------
state ShooterState {
wpn_reg: Dict = null
wpn_reg: Dict = Dict.new()
wpn_n: int = 0
wpn_rate: words = null
wpn_dmg: words = null
wpn_speed: words = null
wpn_spread: words = null
wpn_pellets: words = null
wpn_pattern: words = null
wpn_ttl: words = null
wpn_pierce: words = null
wpn_homing: words = null
wpn_color: words = null
wpn_rate_base: words = null
wpn_rate: words = words(WPN_MAX)
wpn_dmg: words = words(WPN_MAX)
wpn_speed: words = words(WPN_MAX)
wpn_spread: words = words(WPN_MAX)
wpn_pellets: words = words(WPN_MAX)
wpn_pattern: words = words(WPN_MAX)
wpn_ttl: words = words(WPN_MAX)
wpn_pierce: words = words(WPN_MAX)
wpn_homing: words = words(WPN_MAX)
wpn_color: words = words(WPN_MAX)
wpn_rate_base: words = words(WPN_MAX)
wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire)
}
function wpn_ensure(shooter_st: mut ShooterState) -> void {
if shooter_st.wpn_reg == null {
shooter_st.wpn_reg = Dict.new()
shooter_st.wpn_rate = words(WPN_MAX); shooter_st.wpn_dmg = words(WPN_MAX); shooter_st.wpn_speed = words(WPN_MAX)
shooter_st.wpn_spread = words(WPN_MAX); shooter_st.wpn_pellets = words(WPN_MAX); shooter_st.wpn_pattern = words(WPN_MAX)
shooter_st.wpn_ttl = words(WPN_MAX); shooter_st.wpn_pierce = words(WPN_MAX); shooter_st.wpn_homing = words(WPN_MAX)
shooter_st.wpn_color = words(WPN_MAX); shooter_st.wpn_rate_base = words(WPN_MAX)
}
}
# Weapon.def(name, fire_rate, damage, speed, spread, pellets, pattern) — register a
# gun. pattern: 0 single/aimed, 1 spread cone, 2 ring, 3 spiral. Returns the def id.
@Namespace(Weapon) function weapon_def(shooter_st: mut ShooterState, name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int {
wpn_ensure(shooter_st)
if shooter_st.wpn_n >= WPN_MAX { return -1 }
let id = shooter_st.wpn_n
shooter_st.wpn_rate[id] = fire_rate; shooter_st.wpn_dmg[id] = damage; shooter_st.wpn_speed[id] = speed
@ -112,36 +102,35 @@ function wpn_ensure(shooter_st: mut ShooterState) -> void {
return id
}
@Namespace(Weapon) function weapon_id(shooter_st: mut ShooterState, name: pointer) -> int {
wpn_ensure(shooter_st)
return Dict.get_or(shooter_st.wpn_reg, name, -1)
}
# Weapon.set_rate(id, frames) — frames between shots (a fire-rate upgrade changes it in place).
@Namespace(Weapon) function weapon_set_rate(shooter_st: mut ShooterState, id: int, frames: int) -> void {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_rate[id] = frames }
if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_rate[id] = frames }
}
@Namespace(Weapon) function weapon_rate(shooter_st: mut ShooterState, id: int) -> int {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { return shooter_st.wpn_rate[id] }; return 0
if (id >= 0) and (id < WPN_MAX) { return shooter_st.wpn_rate[id] }; return 0
}
# Weapon.reset(id) — back to the definition: no pierce, no homing, the defined fire rate.
@Namespace(Weapon) function weapon_reset(shooter_st: mut ShooterState, id: int) -> void {
wpn_ensure(shooter_st); if (id < 0) or (id >= WPN_MAX) { return }
if (id < 0) or (id >= WPN_MAX) { return }
shooter_st.wpn_pierce[id] = 0; shooter_st.wpn_homing[id] = 0; shooter_st.wpn_rate[id] = shooter_st.wpn_rate_base[id]
}
# Weapon.set_color(id, color) — the colour the engine draws this weapon's shots in.
@Namespace(Weapon) function weapon_set_color(shooter_st: mut ShooterState, id: int, color: int) -> void {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_color[id] = color }
if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_color[id] = color }
}
# Weapon.set_ttl(id, frames) — projectile lifetime for a weapon (range control).
@Namespace(Weapon) function weapon_set_ttl(shooter_st: mut ShooterState, id: int, frames: int) -> void {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_ttl[id] = frames }
if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_ttl[id] = frames }
}
# Weapon.set_pierce(id, n) — a shot passes through n targets before it dies.
@Namespace(Weapon) function weapon_set_pierce(shooter_st: mut ShooterState, id: int, n: int) -> void {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_pierce[id] = n }
if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_pierce[id] = n }
}
# Weapon.set_homing(id, deg) — max degrees/frame a shot turns toward an enemy.
@Namespace(Weapon) function weapon_set_homing(shooter_st: mut ShooterState, id: int, deg: int) -> void {
wpn_ensure(shooter_st); if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_homing[id] = deg }
if (id >= 0) and (id < WPN_MAX) { shooter_st.wpn_homing[id] = deg }
}
# ---------------------------------------------------------------------------
@ -534,7 +523,6 @@ function sh_spawn_bullet(shooter_st: ShooterState, owner: int, fac: int, px: int
@EngineSystem(Weapon, Update) function esys_weapon(shooter_st: mut ShooterState) -> void {
let P = World.prop_id("Weapon")
if P < 0 { return }
wpn_ensure(shooter_st)
var e = World.query_next(P, 0)
while e >= 0 {
let cd = World.get(e, P, World.field_id(P, "cooldown"))