refactor(runtime,packages,examples): named constants, package enums, idiom sweep
- runtime: HEADLESS_FRAME_PATH, STICK_DEADZONE / STICK_LEFT_X/Y, key and byte codes as char literals throughout (`k == 'w'`, `fill(rt_map, ' ', …)`) - ludic.gameplay/stats: drop the duplicate `stat_field` (it answered "atk" for every build stat); Stats.base uses stats_field_name - ludic.shooter: compare aim modes and fire patterns with AimMode.* and WeaponPattern.* instead of raw ints; STICK_RIGHT_X/Y - ludic.npcai: DecisionMade / brain_set_state use AiState.* - examples/games/menu.ludic uses Font.load / Ui.* with FONT_PATH and BACKDROP named; strings.ludic header says what it prints - whole tree: `x = x + 1` → `x += 1` (single-term right-hand sides only), `0 - x` → `-x`, ASCII codes → char literals; every .ludic and every ```ludic fence reformatted with the fixed formatter (whitespace only) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e3e1bc7784
commit
bf36bc8a8f
103 changed files with 1256 additions and 1252 deletions
|
|
@ -53,7 +53,7 @@ var combat_pending: int = 0
|
|||
# the attacker's build: damage_pct, then a crit_pct chance to double
|
||||
var raw = amount * stats_field(src, "damage_pct", 100) / 100
|
||||
let crit = stats_field(src, "crit_pct", 0)
|
||||
if (crit > 0) and Random.chance(percent: crit) { raw = raw * 2; emit Crit(target: target, src: src) }
|
||||
if (crit > 0) and Random.chance(percent: crit) { raw *= 2; emit Crit(target: target, src: src) }
|
||||
combat_pending = combat_mitigate(target, raw)
|
||||
if emit DamageAboutToApply(target: target, src: src, amount: combat_pending) != 0 { return 0 }
|
||||
var dmg = combat_pending
|
||||
|
|
|
|||
|
|
@ -55,10 +55,10 @@ function fac_ensure() -> void {
|
|||
# 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 }
|
||||
if P < 0 { return -1 }
|
||||
if World.has(e, P) == 0 { return -1 }
|
||||
let f = World.field_id(P, "id")
|
||||
if f < 0 { return 0 - 1 }
|
||||
if f < 0 { return -1 }
|
||||
return World.get(e, P, f)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,21 +89,11 @@ model ModifierFx { Modifier }
|
|||
}
|
||||
}
|
||||
|
||||
# map a stat code to the matching Stats field name.
|
||||
function stat_field(stat: int) -> pointer {
|
||||
if stat == 0 { return "max_hp" }
|
||||
if stat == 1 { return "atk" }
|
||||
if stat == 2 { return "def" }
|
||||
if stat == 3 { return "spd" }
|
||||
if stat == 4 { return "max_mp" }
|
||||
return "atk"
|
||||
}
|
||||
|
||||
# Stats.base(e, stat) — the raw stored field, no modifiers.
|
||||
@Namespace(Stats) function stats_base(e: int, stat: int) -> int {
|
||||
let P = World.prop_id("Stats")
|
||||
if P < 0 { return 0 }
|
||||
let f = World.field_id(P, stat_field(stat))
|
||||
let f = World.field_id(P, stats_field_name(stat))
|
||||
if f < 0 { return 0 }
|
||||
return World.get(e, P, f)
|
||||
}
|
||||
|
|
@ -123,7 +113,7 @@ function stat_field(stat: int) -> pointer {
|
|||
while m >= 0 {
|
||||
if (World.get(m, P, ft) == e) and (World.get(m, P, fs) == stat) {
|
||||
let amt = World.get(m, P, fa)
|
||||
if World.get(m, P, fo) == 1 { pct = pct + amt } else { flat = flat + amt }
|
||||
if World.get(m, P, fo) == 1 { pct += amt } else { flat += amt }
|
||||
}
|
||||
m = World.query_next(P, m + 1)
|
||||
}
|
||||
|
|
@ -133,7 +123,7 @@ function stat_field(stat: int) -> pointer {
|
|||
# Stats.modify(target, stat, op, amount, ttl) — push a modifier onto the stack.
|
||||
@Namespace(Stats) function stats_modify(target: int, stat: int, op: int, amount: int, ttl: int) -> int {
|
||||
let m = World.model_id("ModifierFx")
|
||||
if m < 0 { return 0 - 1 }
|
||||
if m < 0 { return -1 }
|
||||
let e = World.spawn(m)
|
||||
let P = World.prop_id("Modifier")
|
||||
World.set(e, P, World.field_id(P, "target"), target)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ property Brain {
|
|||
home_x: int = 0, home_y: int = 0, seed: int = 1,
|
||||
hunt_blind: int = 0 # 1 = with no target in sight, seek the nearest hostile anyway (no idle patrol)
|
||||
}
|
||||
property Follower { leader: int = 0 - 1, distance: int = 40, mode: int = 0 } # mode 0 follow,1 guard,2 aggressive
|
||||
property Follower { leader: int = -1, distance: int = 40, mode: int = 0 } # mode 0 follow,1 guard,2 aggressive
|
||||
property Steering { separate: int = 0, cohere: int = 0, align: int = 0, radius: int = 48 }
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -59,7 +59,7 @@ function ai_has(prop: pointer, e: int) -> int {
|
|||
let P = World.prop_id(prop); if P < 0 { return 0 }
|
||||
return World.has(e, P)
|
||||
}
|
||||
function sign_i(a: int) -> int { if a > 0 { return 1 }; if a < 0 { return 0 - 1 }; return 0 }
|
||||
function sign_i(a: int) -> int { if a > 0 { return 1 }; if a < 0 { return -1 }; return 0 }
|
||||
|
||||
# optional line-of-sight over the tilemap (only when a Solids config exists);
|
||||
# without a tilemap the field is open, so LOS is always clear.
|
||||
|
|
@ -104,7 +104,7 @@ function ai_los(px: int, py: int, tx: int, ty: int) -> int {
|
|||
let fac = Faction.id_of(e)
|
||||
# nearest hostile in range + LOS
|
||||
let PF = World.prop_id("Faction")
|
||||
var best = 0 - 1
|
||||
var best = -1
|
||||
var bestd = 0
|
||||
var o = World.query_next(PF, 0)
|
||||
while o >= 0 {
|
||||
|
|
@ -196,8 +196,8 @@ function brain_seek(e: int, tx: int, ty: int) -> void {
|
|||
function brain_flee(e: int, tx: int, ty: int) -> void {
|
||||
let px = ai_get("Position", e, "x")
|
||||
let py = ai_get("Position", e, "y")
|
||||
ai_set("Brain", e, "intent_x", 0 - sign_i(tx - px))
|
||||
ai_set("Brain", e, "intent_y", 0 - sign_i(ty - py))
|
||||
ai_set("Brain", e, "intent_x", -sign_i(tx - px))
|
||||
ai_set("Brain", e, "intent_y", -sign_i(ty - py))
|
||||
}
|
||||
function brain_stop(e: int) -> void { ai_set("Brain", e, "intent_x", 0); ai_set("Brain", e, "intent_y", 0) }
|
||||
|
||||
|
|
@ -257,20 +257,20 @@ function ai_decide_fsm(e: int) -> void {
|
|||
let tx = ai_get("Memory", e, "last_x")
|
||||
let ty = ai_get("Memory", e, "last_y")
|
||||
if brain_low_hp(e, ai_get("Brain", e, "flee_pct")) == 1 {
|
||||
if emit DecisionMade(e: e, action: 3) == 0 { brain_set_state(e, 3); brain_flee(e, tx, ty) }
|
||||
if emit DecisionMade(e: e, action: AiState.Flee) == 0 { brain_set_state(e, AiState.Flee); brain_flee(e, tx, ty) }
|
||||
} else {
|
||||
if d2 <= ar * ar {
|
||||
if emit DecisionMade(e: e, action: 2) == 0 { brain_set_state(e, 2); brain_stop(e); ai_set("Brain", e, "want_fire", 1) }
|
||||
if emit DecisionMade(e: e, action: AiState.Attack) == 0 { brain_set_state(e, AiState.Attack); brain_stop(e); ai_set("Brain", e, "want_fire", 1) }
|
||||
} else {
|
||||
if emit DecisionMade(e: e, action: 1) == 0 { brain_set_state(e, 1); brain_seek(e, tx, ty) }
|
||||
if emit DecisionMade(e: e, action: AiState.Chase) == 0 { brain_set_state(e, AiState.Chase); brain_seek(e, tx, ty) }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let prey = brain_nearest_hostile(e)
|
||||
if (ai_get("Brain", e, "hunt_blind") == 1) and (prey >= 0) {
|
||||
if emit DecisionMade(e: e, action: 1) == 0 { brain_set_state(e, 1); brain_seek(e, ai_get("Position", prey, "x"), ai_get("Position", prey, "y")) }
|
||||
if emit DecisionMade(e: e, action: AiState.Chase) == 0 { brain_set_state(e, AiState.Chase); brain_seek(e, ai_get("Position", prey, "x"), ai_get("Position", prey, "y")) }
|
||||
} else {
|
||||
if emit DecisionMade(e: e, action: 0) == 0 { brain_set_state(e, 0); brain_wander(e) }
|
||||
if emit DecisionMade(e: e, action: AiState.Patrol) == 0 { brain_set_state(e, AiState.Patrol); brain_wander(e) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -278,11 +278,11 @@ function ai_decide_fsm(e: int) -> void {
|
|||
function brain_nearest_hostile(e: int) -> int {
|
||||
let PF = World.prop_id("Faction")
|
||||
let PP = World.prop_id("Position")
|
||||
if (PF < 0) or (PP < 0) { return 0 - 1 }
|
||||
if (PF < 0) or (PP < 0) { return -1 }
|
||||
let fac = Faction.id_of(e)
|
||||
let px = ai_get("Position", e, "x")
|
||||
let py = ai_get("Position", e, "y")
|
||||
var best = 0 - 1
|
||||
var best = -1
|
||||
var bestd = 0
|
||||
var o = World.query_next(PF, 0)
|
||||
while o >= 0 {
|
||||
|
|
@ -338,11 +338,11 @@ function ai_decide_bt(e: int) -> void {
|
|||
let ar = ai_get("Brain", e, "attack_range")
|
||||
if ai_get("Memory", e, "has_target") == 1 { # leaf: see_target
|
||||
if brain_target_d2(e) <= ar * ar { # leaf: in_range
|
||||
if emit DecisionMade(e: e, action: 2) == 0 { brain_set_state(e, 2); brain_stop(e); ai_set("Brain", e, "want_fire", 1); return }
|
||||
if emit DecisionMade(e: e, action: AiState.Attack) == 0 { brain_set_state(e, AiState.Attack); brain_stop(e); ai_set("Brain", e, "want_fire", 1); return }
|
||||
}
|
||||
if emit DecisionMade(e: e, action: 1) == 0 { brain_set_state(e, 1); brain_seek(e, tx, ty); return }
|
||||
if emit DecisionMade(e: e, action: AiState.Chase) == 0 { brain_set_state(e, AiState.Chase); brain_seek(e, tx, ty); return }
|
||||
}
|
||||
if emit DecisionMade(e: e, action: 0) == 0 { brain_set_state(e, 0); brain_wander(e) }
|
||||
if emit DecisionMade(e: e, action: AiState.Patrol) == 0 { brain_set_state(e, AiState.Patrol); brain_wander(e) }
|
||||
}
|
||||
|
||||
# ===========================================================================
|
||||
|
|
@ -404,8 +404,8 @@ function ai_decide_bt(e: int) -> void {
|
|||
let dx = ox - px; let dy = oy - py
|
||||
let d2 = dx * dx + dy * dy
|
||||
if d2 <= radius * radius {
|
||||
sepx = sepx - sign_i(dx); sepy = sepy - sign_i(dy)
|
||||
cx = cx + ox; cy = cy + oy; n = n + 1
|
||||
sepx -= sign_i(dx); sepy -= sign_i(dy)
|
||||
cx += ox; cy += oy; n += 1
|
||||
}
|
||||
}
|
||||
o = World.query_next(PS, o + 1)
|
||||
|
|
@ -413,8 +413,8 @@ function ai_decide_bt(e: int) -> void {
|
|||
if n > 0 {
|
||||
var ix = ai_get("Brain", e, "intent_x")
|
||||
var iy = ai_get("Brain", e, "intent_y")
|
||||
if wsep > 0 { ix = ix + sign_i(sepx); iy = iy + sign_i(sepy) }
|
||||
if wcoh > 0 { ix = ix + sign_i(cx / n - px); iy = iy + sign_i(cy / n - py) }
|
||||
if wsep > 0 { ix += sign_i(sepx); iy += sign_i(sepy) }
|
||||
if wcoh > 0 { ix += sign_i(cx / n - px); iy += sign_i(cy / n - py) }
|
||||
ai_set("Brain", e, "intent_x", sign_i(ix))
|
||||
ai_set("Brain", e, "intent_y", sign_i(iy))
|
||||
}
|
||||
|
|
@ -457,7 +457,7 @@ function ai_decide_bt(e: int) -> void {
|
|||
@Namespace(Ai) function ai_state(e: int) -> int { return ai_get("Brain", e, "state") }
|
||||
@Namespace(Ai) function ai_target(e: int) -> int {
|
||||
if ai_get("Memory", e, "has_target") == 1 { return ai_get("Memory", e, "target") }
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
@Namespace(Follower) function follower_set(e: int, leader: int, distance: int, mode: int) -> void {
|
||||
ai_set("Follower", e, "leader", leader)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ property Platformer {
|
|||
friction: int = 50, # ground stopping, % of speed shed / frame when idle
|
||||
jump_height: int = 4, # tiles; gravity + impulse are derived from this + apex
|
||||
apex_frames: int = 14, # frames to the top of the jump (defines gravity — feel)
|
||||
fall_gravity_mul: int = 160, # 100 = symmetric; >100 = snappier fall (Celeste)
|
||||
fall_gravity_mul: int = 160, # 100 = symmetric; >100 = snappier fall (Celeste)
|
||||
coyote_frames: int = 6, # grace frames to still jump after leaving a ledge
|
||||
jump_buffer_frames: int = 6, # grace frames to buffer a jump pressed before landing
|
||||
jump_buffer_frames: int = 6, # grace frames to buffer a jump pressed before landing
|
||||
max_fall: int = 8, # terminal fall speed, px/frame
|
||||
air_jumps: int = 0, # extra mid-air jumps (1 = double jump, N = multi)
|
||||
policy: int = 0, # gravity profile (lever 6): 0 asymmetric, 1 symmetric, 2 floaty
|
||||
|
|
@ -129,8 +129,8 @@ function bd_set(e: int, name: pointer, v: int) -> void {
|
|||
var e = World.query_next(P, 0)
|
||||
while e >= 0 {
|
||||
var wx = 0
|
||||
if Input.down("move_right") { wx = wx + 1 }
|
||||
if Input.down("move_left") { wx = wx - 1 }
|
||||
if Input.down("move_right") { wx += 1 }
|
||||
if Input.down("move_left") { wx -= 1 }
|
||||
pf_set(e, "want_x", wx)
|
||||
var wj = 0; if Input.pressed("jump") { wj = 1 }
|
||||
pf_set(e, "want_jump", wj)
|
||||
|
|
@ -185,14 +185,14 @@ function bd_set(e: int, name: pointer, v: int) -> void {
|
|||
let apex = pf_get(e, "apex_frames")
|
||||
var g = plat_g(jh, apex, tile)
|
||||
let pol = pf_get(e, "policy")
|
||||
if pol == 2 { g = g / 2 } # floaty
|
||||
if pol == 2 { g /= 2 } # floaty
|
||||
let vy = bd_get(e, "vy")
|
||||
if vy > 0 { # falling
|
||||
var fm = pf_get(e, "fall_gravity_mul")
|
||||
if pol == 1 { fm = 100 } # symmetric profile ignores the fall multiplier
|
||||
g = g * fm / 100
|
||||
} else {
|
||||
if pf_get(e, "hold_jump") == 0 { g = g * 2 } # released while rising -> short hop
|
||||
if pf_get(e, "hold_jump") == 0 { g *= 2 } # released while rising -> short hop
|
||||
}
|
||||
bd_set(e, "gravity", g)
|
||||
bd_set(e, "max_fall", pf_get(e, "max_fall") * PLAT_Q)
|
||||
|
|
@ -205,7 +205,7 @@ function plat_do_jump(e: int, kind: int) -> bool {
|
|||
if emit JumpRequested(e: e, kind: kind) != 0 { return false }
|
||||
let tile = plat_tile()
|
||||
let v0 = plat_v0(pf_get(e, "jump_height"), pf_get(e, "apex_frames"), tile)
|
||||
bd_set(e, "vy", 0 - v0)
|
||||
bd_set(e, "vy", -v0)
|
||||
pf_set(e, "buffer_t", 0)
|
||||
pf_set(e, "coyote_t", 0)
|
||||
emit JumpPerformed(e: e, kind: kind)
|
||||
|
|
@ -280,7 +280,7 @@ function plat_do_jump(e: int, kind: int) -> bool {
|
|||
# state
|
||||
var st = 0
|
||||
if grounded == 1 {
|
||||
var ax = vx; if ax < 0 { ax = 0 - ax }
|
||||
var ax = vx; if ax < 0 { ax = -ax }
|
||||
if ax > (PLAT_Q / 4) { st = 1 } else { st = 0 } # running vs idle (>0.25 px/frame)
|
||||
} else {
|
||||
if vy < 0 { st = 2 } else { st = 3 } # rise vs fall
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ event PlatformCrumbled { e: int = 0 }
|
|||
# platform's top edge within the horizontal span.
|
||||
function plat_rider_on(p: int) -> int {
|
||||
let PB = World.prop_id("Body")
|
||||
if PB < 0 { return 0 - 1 }
|
||||
if PB < 0 { return -1 }
|
||||
let px = sc_pos(p, "x") + sc_col(p, "offx")
|
||||
let py = sc_pos(p, "y") + sc_col(p, "offy")
|
||||
let pw = sc_col(p, "w")
|
||||
|
|
@ -79,7 +79,7 @@ function plat_rider_on(p: int) -> int {
|
|||
}
|
||||
b = World.query_next(PB, b + 1)
|
||||
}
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
|
||||
@EngineSystem(Platform, FixedUpdate) function esys_platform() -> void {
|
||||
|
|
@ -103,8 +103,8 @@ function plat_rider_on(p: int) -> int {
|
|||
var tx = bx; var ty = by
|
||||
if dir < 0 { tx = ax; ty = ay }
|
||||
var dx = 0; var dy = 0
|
||||
if ox < tx { dx = speed } else { if ox > tx { dx = 0 - speed } }
|
||||
if oy < ty { dy = speed } else { if oy > ty { dy = 0 - speed } }
|
||||
if ox < tx { dx = speed } else { if ox > tx { dx = -speed } }
|
||||
if oy < ty { dy = speed } else { if oy > ty { dy = -speed } }
|
||||
# clamp the step so we do not overshoot the endpoint
|
||||
if dx > 0 { if ox + dx > tx { dx = tx - ox } }
|
||||
if dx < 0 { if ox + dx < tx { dx = tx - ox } }
|
||||
|
|
@ -120,7 +120,7 @@ function plat_rider_on(p: int) -> int {
|
|||
}
|
||||
# flip direction at an endpoint
|
||||
if (sc_pos(e, "x") == tx) and (sc_pos(e, "y") == ty) {
|
||||
World.set(e, P, World.field_id(P, "dir"), 0 - dir)
|
||||
World.set(e, P, World.field_id(P, "dir"), -dir)
|
||||
}
|
||||
}
|
||||
if kind == 3 { # crumble
|
||||
|
|
@ -222,7 +222,7 @@ event Sprang { body: int = 0, spring: int = 0, power: int = 0 }
|
|||
let body = World.get(e, PC, fhit)
|
||||
if body >= 0 {
|
||||
let power = World.get(e, P, fpow)
|
||||
sc_body_set(body, "vy", 0 - power * SCAF_Q)
|
||||
sc_body_set(body, "vy", -power * SCAF_Q)
|
||||
sc_body_set(body, "ry", 0)
|
||||
emit Sprang(body: body, spring: e, power: power)
|
||||
}
|
||||
|
|
@ -269,7 +269,7 @@ property Hazard { dmg: int = 1, knockback: int = 6 }
|
|||
let dmg = World.get(e, P, fdmg)
|
||||
if life_hurt(body, e, dmg) > 0 {
|
||||
let kb = World.get(e, P, fkb)
|
||||
sc_body_set(body, "vy", 0 - kb * SCAF_Q) # pop up on hit
|
||||
sc_body_set(body, "vy", -kb * SCAF_Q) # pop up on hit
|
||||
}
|
||||
}
|
||||
e = World.query_next(P, e + 1)
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ namespace Prefs {
|
|||
|
||||
internal function find(key: pointer) -> int {
|
||||
var i = 0
|
||||
while i < prefs_n { if prefs_names[i] == key { return i }; i = i + 1 }
|
||||
return 0 - 1
|
||||
while i < prefs_n { if prefs_names[i] == key { return i }; i += 1 }
|
||||
return -1
|
||||
}
|
||||
|
||||
export function get(key: pointer, fallback: int) -> int {
|
||||
|
|
@ -49,7 +49,7 @@ namespace Prefs {
|
|||
if prefs_n >= PREFS_MAX { return }
|
||||
i = prefs_n
|
||||
prefs_names[i] = key
|
||||
prefs_n = prefs_n + 1
|
||||
prefs_n += 1
|
||||
}
|
||||
prefs_vals[i] = value
|
||||
}
|
||||
|
|
@ -71,18 +71,18 @@ namespace Prefs {
|
|||
# digits after `=` on one line -> an int (a leading '-' negates)
|
||||
internal function parse_line(buf: pointer, from: int, to: int) -> void {
|
||||
var eq = from
|
||||
while (eq < to) and (buf[eq] != 61) { eq = eq + 1 } # '='
|
||||
while (eq < to) and (buf[eq] != '=') { eq += 1 } # '='
|
||||
if eq >= to { return }
|
||||
let key = bytes(eq - from + 1)
|
||||
var k = 0
|
||||
while k < eq - from { key[k] = buf[from + k]; k = k + 1 }
|
||||
while k < eq - from { key[k] = buf[from + k]; k += 1 }
|
||||
key[eq - from] = 0
|
||||
var v = 0
|
||||
var neg = false
|
||||
var p = eq + 1
|
||||
if (p < to) and (buf[p] == 45) { neg = true; p = p + 1 } # '-'
|
||||
while (p < to) and (buf[p] >= 48) and (buf[p] <= 57) { v = v * 10 + (buf[p] - 48); p = p + 1 }
|
||||
if neg { v = 0 - v }
|
||||
if (p < to) and (buf[p] == '-') { neg = true; p += 1 } # '-'
|
||||
while (p < to) and (buf[p] >= '0') and (buf[p] <= '9') { v = v * 10 + (buf[p] - 48); p += 1 }
|
||||
if neg { v = -v }
|
||||
set(key, v)
|
||||
}
|
||||
|
||||
|
|
@ -94,15 +94,15 @@ namespace Prefs {
|
|||
var start = 0
|
||||
var i = 0
|
||||
while buf[i] != 0 {
|
||||
if buf[i] == 10 { parse_line(buf, start, i); start = i + 1 } # newline
|
||||
i = i + 1
|
||||
if buf[i] == '\n' { parse_line(buf, start, i); start = i + 1 } # newline
|
||||
i += 1
|
||||
}
|
||||
if i > start { parse_line(buf, start, i) }
|
||||
}
|
||||
|
||||
internal function write_text(f: pointer, s: pointer) -> void {
|
||||
var n = 0
|
||||
while s[n] != 0 { n = n + 1 }
|
||||
while s[n] != 0 { n += 1 }
|
||||
file_write(f, s, n)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ namespace Prefs {
|
|||
var i = 0
|
||||
while i < prefs_n {
|
||||
write_text(f, `{prefs_names[i]}={string(prefs_vals[i])}\n`)
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
file_close(f)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ 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(name: pointer, out_item: pointer, out_qty: int) -> int {
|
||||
craft_ensure()
|
||||
if recipe_n >= CRAFT_MAX { return 0 - 1 }
|
||||
if recipe_n >= CRAFT_MAX { return -1 }
|
||||
let id = recipe_n
|
||||
recipe_out[id] = out_item; recipe_outqty[id] = out_qty
|
||||
recipe_n = recipe_n + 1
|
||||
recipe_n += 1
|
||||
Dict.set(recipe_reg, name, id)
|
||||
return id
|
||||
}
|
||||
@Namespace(Craft) function craft_id(name: pointer) -> int { craft_ensure(); return Dict.get_or(recipe_reg, name, 0 - 1) }
|
||||
@Namespace(Craft) function craft_id(name: pointer) -> int { craft_ensure(); return Dict.get_or(recipe_reg, name, -1) }
|
||||
# Craft.ingredient(recipe_name, item, qty) — add an ingredient requirement.
|
||||
@Namespace(Craft) function craft_ingredient(recipe_name: pointer, item: pointer, qty: int) -> void {
|
||||
craft_ensure()
|
||||
|
|
@ -47,7 +47,7 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
|
|||
if rid < 0 { return }
|
||||
if ing_n >= CRAFT_ING_MAX { return }
|
||||
ing_rid[ing_n] = rid; ing_name[ing_n] = item; ing_qty[ing_n] = qty
|
||||
ing_n = ing_n + 1
|
||||
ing_n += 1
|
||||
}
|
||||
|
||||
# Craft.can(e, recipe_name) — does the owner hold every ingredient?
|
||||
|
|
@ -60,7 +60,7 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
|
|||
if ing_rid[i] == rid {
|
||||
if Inventory.count(e, ing_name[i]) < ing_qty[i] { return 0 }
|
||||
}
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
|
@ -75,7 +75,7 @@ event CraftCompleted { e: int = 0, recipe: int = 0 }
|
|||
var i = 0
|
||||
while i < ing_n {
|
||||
if ing_rid[i] == rid { Inventory.remove(e, ing_name[i], ing_qty[i]) }
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
Inventory.add(e, recipe_out[rid], recipe_outqty[rid])
|
||||
emit CraftCompleted(e: e, recipe: rid)
|
||||
|
|
|
|||
|
|
@ -38,12 +38,12 @@ event DialogEnded { e: int = 0 }
|
|||
# Dialog.tree(name) — register (or fetch) a tree id.
|
||||
@Namespace(Dialog) function dialog_tree(name: pointer) -> int {
|
||||
dlg_ensure()
|
||||
let ex = Dict.get_or(dlg_reg, name, 0 - 1)
|
||||
let ex = Dict.get_or(dlg_reg, name, -1)
|
||||
if ex >= 0 { return ex }
|
||||
if dlg_tree_n >= DLG_TREE_MAX { return 0 - 1 }
|
||||
if dlg_tree_n >= DLG_TREE_MAX { return -1 }
|
||||
let id = dlg_tree_n
|
||||
dlg_tree_start[id] = 0 - 1
|
||||
dlg_tree_n = dlg_tree_n + 1
|
||||
dlg_tree_start[id] = -1
|
||||
dlg_tree_n += 1
|
||||
Dict.set(dlg_reg, name, id)
|
||||
return id
|
||||
}
|
||||
|
|
@ -52,21 +52,21 @@ event DialogEnded { e: int = 0 }
|
|||
@Namespace(Dialog) function dialog_node(tree_name: pointer, text: pointer) -> int {
|
||||
dlg_ensure()
|
||||
let tid = dialog_tree(tree_name)
|
||||
if tid < 0 { return 0 - 1 }
|
||||
if dlg_node_n >= DLG_NODE_MAX { return 0 - 1 }
|
||||
if tid < 0 { return -1 }
|
||||
if dlg_node_n >= DLG_NODE_MAX { return -1 }
|
||||
let id = dlg_node_n
|
||||
dlg_node_tree[id] = tid; dlg_node_text[id] = text
|
||||
dlg_node_n = dlg_node_n + 1
|
||||
dlg_node_n += 1
|
||||
if dlg_tree_start[tid] < 0 { dlg_tree_start[tid] = id }
|
||||
return id
|
||||
}
|
||||
# Dialog.choice(node, label, next_node) — add a branch (next_node -1 ends).
|
||||
@Namespace(Dialog) function dialog_choice(node: int, label: pointer, next_node: int) -> int {
|
||||
dlg_ensure()
|
||||
if dlg_choice_n >= DLG_CHOICE_MAX { return 0 - 1 }
|
||||
if dlg_choice_n >= DLG_CHOICE_MAX { return -1 }
|
||||
let id = dlg_choice_n
|
||||
dlg_choice_node[id] = node; dlg_choice_label[id] = label; dlg_choice_next[id] = next_node
|
||||
dlg_choice_n = dlg_choice_n + 1
|
||||
dlg_choice_n += 1
|
||||
return id
|
||||
}
|
||||
|
||||
|
|
@ -101,7 +101,7 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
|
|||
if dlg_get(e, "active") == 0 { return 0 }
|
||||
let node = dlg_get(e, "node")
|
||||
var n = 0; var i = 0
|
||||
while i < dlg_choice_n { if dlg_choice_node[i] == node { n = n + 1 }; i = i + 1 }
|
||||
while i < dlg_choice_n { if dlg_choice_node[i] == node { n += 1 }; i += 1 }
|
||||
return n
|
||||
}
|
||||
# Dialog.choose(e, i) — take the i-th choice (veto-able); advance or end.
|
||||
|
|
@ -109,12 +109,12 @@ function dlg_set(e: int, name: pointer, v: int) -> void {
|
|||
dlg_ensure()
|
||||
if dlg_get(e, "active") == 0 { return 0 }
|
||||
let node = dlg_get(e, "node")
|
||||
var seen = 0; var i = 0; var pick = 0 - 1
|
||||
var seen = 0; var i = 0; var pick = -1
|
||||
while i < dlg_choice_n {
|
||||
if dlg_choice_node[i] == node {
|
||||
if seen == idx { pick = i; i = dlg_choice_n } else { seen = seen + 1 }
|
||||
if seen == idx { pick = i; i = dlg_choice_n } else { seen += 1 }
|
||||
}
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
if pick < 0 { return 0 }
|
||||
if emit DialogChoice(e: e, node: node, choice: idx) != 0 { return 0 }
|
||||
|
|
|
|||
|
|
@ -26,14 +26,14 @@ function item_ensure() -> void {
|
|||
# Item.def(name, stack_max, weight, value) — register an item template. Returns id.
|
||||
@Namespace(Item) function item_def(name: pointer, stack_max: int, weight: int, value: int) -> int {
|
||||
item_ensure()
|
||||
if item_n >= INV_MAX { return 0 - 1 }
|
||||
if item_n >= INV_MAX { return -1 }
|
||||
let id = item_n
|
||||
item_stackmax[id] = stack_max; item_weight[id] = weight; item_value[id] = value
|
||||
item_n = item_n + 1
|
||||
item_n += 1
|
||||
Dict.set(item_reg, name, id)
|
||||
return id
|
||||
}
|
||||
@Namespace(Item) function item_id(name: pointer) -> int { item_ensure(); return Dict.get_or(item_reg, name, 0 - 1) }
|
||||
@Namespace(Item) function item_id(name: pointer) -> int { item_ensure(); return Dict.get_or(item_reg, name, -1) }
|
||||
@Namespace(Item) function item_value_of(name: pointer) -> int {
|
||||
let id = item_id(name); if id < 0 { return 0 }
|
||||
return item_value[id]
|
||||
|
|
@ -97,7 +97,7 @@ event cancellable ItemUse { e: int = 0, item: int = 0 } # veto a use (
|
|||
@Namespace(Inventory) function inventory_spend_gold(e: int, n: int) -> int {
|
||||
let g = inventory_gold(e)
|
||||
if g < n { return 0 }
|
||||
inventory_add_gold(e, 0 - n)
|
||||
inventory_add_gold(e, -n)
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ function mv_body_set(e: int, name: pointer, v: int) -> void {
|
|||
let P = World.prop_id("Body"); if P < 0 { return }
|
||||
let f = World.field_id(P, name); if f >= 0 { World.set(e, P, f, v) }
|
||||
}
|
||||
function mv_sign(a: int) -> int { if a > 0 { return 1 }; if a < 0 { return 0 - 1 }; return 0 }
|
||||
function mv_sign(a: int) -> int { if a > 0 { return 1 }; if a < 0 { return -1 }; return 0 }
|
||||
|
||||
# is the destination cell walkable? Uses the Solids tilemap when configured,
|
||||
# otherwise the whole field is open.
|
||||
|
|
@ -159,19 +159,19 @@ function mover_free(e: int) -> void {
|
|||
let ax = mv_pos(e, "x") + mv_get(e, "facing_x") * step
|
||||
let ay = mv_pos(e, "y") + mv_get(e, "facing_y") * step
|
||||
let PP = World.prop_id("Position")
|
||||
if PP < 0 { return 0 - 1 }
|
||||
if PP < 0 { return -1 }
|
||||
var o = World.query_next(PP, 0)
|
||||
while o >= 0 {
|
||||
if o != e {
|
||||
let ox = World.get(o, PP, World.field_id(PP, "x"))
|
||||
let oy = World.get(o, PP, World.field_id(PP, "y"))
|
||||
let dx = ox - ax; let dy = oy - ay
|
||||
var adx = dx; if adx < 0 { adx = 0 - adx }
|
||||
var ady = dy; if ady < 0 { ady = 0 - ady }
|
||||
var adx = dx; if adx < 0 { adx = -adx }
|
||||
var ady = dy; if ady < 0 { ady = -ady }
|
||||
if (adx < step) and (ady < step) { emit Interacted(e: e, target: o); return o }
|
||||
}
|
||||
o = World.query_next(PP, o + 1)
|
||||
}
|
||||
emit Interacted(e: e, target: 0 - 1)
|
||||
return 0 - 1
|
||||
emit Interacted(e: e, target: -1)
|
||||
return -1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const PZ_Q: int = 65536
|
|||
property Pushable { }
|
||||
property Switch { on: int = 0 }
|
||||
property Pressure { on: int = 0, step: int = 16 }
|
||||
property Gate { open: int = 0, src: int = 0 - 1 }
|
||||
property Gate { open: int = 0, src: int = -1 }
|
||||
|
||||
event BlockPushed { block: int = 0, dx: int = 0, dy: int = 0 }
|
||||
event cancellable PushBlocked { block: int = 0 }
|
||||
|
|
@ -27,13 +27,13 @@ function pz_pos_set(e: int, name: pointer, v: int) -> void {
|
|||
# the pushable whose cell is exactly (x, y), or -1.
|
||||
function pz_block_at(x: int, y: int) -> int {
|
||||
let P = World.prop_id("Pushable")
|
||||
if P < 0 { return 0 - 1 }
|
||||
if P < 0 { return -1 }
|
||||
var e = World.query_next(P, 0)
|
||||
while e >= 0 {
|
||||
if (pz_pos(e, "x") == x) and (pz_pos(e, "y") == y) { return e }
|
||||
e = World.query_next(P, e + 1)
|
||||
}
|
||||
return 0 - 1
|
||||
return -1
|
||||
}
|
||||
|
||||
# reuse the Mover module's tilemap walkability check when present.
|
||||
|
|
@ -120,9 +120,9 @@ function pz_source_on(src: int) -> int {
|
|||
if (o != e) and (World.has(o, P) == 0) {
|
||||
let ox = World.get(o, PP, World.field_id(PP, "x"))
|
||||
let oy = World.get(o, PP, World.field_id(PP, "y"))
|
||||
var adx = ox - px; if adx < 0 { adx = 0 - adx }
|
||||
var ady = oy - py; if ady < 0 { ady = 0 - ady }
|
||||
if (adx < step) and (ady < step) { on = 1; o = 0 - 1 }
|
||||
var adx = ox - px; if adx < 0 { adx = -adx }
|
||||
var ady = oy - py; if ady < 0 { ady = -ady }
|
||||
if (adx < step) and (ady < step) { on = 1; o = -1 }
|
||||
}
|
||||
if o >= 0 { o = World.query_next(PP, o + 1) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,24 +41,24 @@ 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(name: pointer) -> int {
|
||||
quest_ensure()
|
||||
if quest_n >= QUEST_MAX { return 0 - 1 }
|
||||
if quest_n >= QUEST_MAX { return -1 }
|
||||
let id = quest_n
|
||||
quest_state[id] = 0
|
||||
quest_n = quest_n + 1
|
||||
quest_n += 1
|
||||
Dict.set(quest_reg, name, id)
|
||||
return id
|
||||
}
|
||||
@Namespace(Quest) function quest_id(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_reg, name, 0 - 1) }
|
||||
@Namespace(Quest) function quest_id(name: pointer) -> int { quest_ensure(); return Dict.get_or(quest_reg, name, -1) }
|
||||
# Quest.objective(quest_name, kind, target, need) — add an objective.
|
||||
@Namespace(Quest) function quest_objective(quest_name: pointer, kind: int, target: int, need: int) -> int {
|
||||
quest_ensure()
|
||||
let qid = quest_id(quest_name)
|
||||
if qid < 0 { return 0 - 1 }
|
||||
if obj_n >= OBJ_MAX { return 0 - 1 }
|
||||
if qid < 0 { return -1 }
|
||||
if obj_n >= OBJ_MAX { return -1 }
|
||||
let id = obj_n
|
||||
obj_qid[id] = qid; obj_kind[id] = kind; obj_target[id] = target
|
||||
obj_need[id] = need; obj_have[id] = 0
|
||||
obj_n = obj_n + 1
|
||||
obj_n += 1
|
||||
return id
|
||||
}
|
||||
# Quest.start(name) — activate a quest (emits QuestStarted).
|
||||
|
|
@ -72,7 +72,7 @@ event ObjectiveProgressed { quest: int = 0, objective: int = 0, have: int = 0, n
|
|||
@Namespace(Quest) function quest_state_of(name: pointer) -> int {
|
||||
quest_ensure()
|
||||
let qid = quest_id(name)
|
||||
if qid < 0 { return 0 - 1 }
|
||||
if qid < 0 { return -1 }
|
||||
return quest_state[qid]
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ function quest_all_done(qid: int) -> int {
|
|||
var i = 0
|
||||
while i < obj_n {
|
||||
if obj_qid[i] == qid { if obj_have[i] < obj_need[i] { return 0 } }
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
|
@ -98,7 +98,7 @@ function quest_all_done(qid: int) -> int {
|
|||
if obj_kind[i] == kind {
|
||||
if (obj_target[i] == 0) or (obj_target[i] == target) {
|
||||
if obj_have[i] < obj_need[i] {
|
||||
obj_have[i] = obj_have[i] + amount
|
||||
obj_have[i] += amount
|
||||
if obj_have[i] > obj_need[i] { obj_have[i] = obj_need[i] }
|
||||
emit ObjectiveProgressed(quest: qid, objective: i, have: obj_have[i], need: obj_need[i])
|
||||
if quest_all_done(qid) == 1 {
|
||||
|
|
@ -109,7 +109,7 @@ function quest_all_done(qid: int) -> int {
|
|||
}
|
||||
}
|
||||
}
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ event StatusExpired { target: int = 0, kind: int = 0 }
|
|||
# Status.apply(target, kind, amount, interval, ttl) — attach a timed effect.
|
||||
@Namespace(Status) function status_apply(target: int, kind: int, amount: int, interval: int, ttl: int) -> int {
|
||||
let m = World.model_id("StatusFx")
|
||||
if m < 0 { return 0 - 1 }
|
||||
if m < 0 { return -1 }
|
||||
let e = World.spawn(m)
|
||||
let P = World.prop_id("Status")
|
||||
World.set(e, P, World.field_id(P, "target"), target)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ const WPN_MAX: int = 64
|
|||
# ---------------------------------------------------------------------------
|
||||
enum AimMode { Mouse, RightStick, MoveDirection, NearestEnemy, Manual, Auto } # TopDown.aim_mode; Auto = mouse, or the right stick while a pad is connected
|
||||
enum WeaponPattern { Single, Cone, Ring, Spiral } # Weapon.def pattern
|
||||
|
||||
# gamepad axis indices as Input.pad_axis numbers them: left stick 0/1, right stick 2/3
|
||||
const STICK_RIGHT_X: int = 2
|
||||
const STICK_RIGHT_Y: int = 3
|
||||
property TopDown {
|
||||
move_speed: int = 4, # px/frame
|
||||
accel: int = 80, # responsiveness %, gap closed / frame
|
||||
|
|
@ -41,7 +45,7 @@ property TopDown {
|
|||
|
||||
# a Weapon instance points at a registry entry (def_id). cooldown counts down to
|
||||
# the next allowed shot; ammo -1 = infinite; phase drives spiral emitters.
|
||||
property Weapon { def_id: int = 0, ammo: int = 0 - 1, cooldown: int = 0, phase: int = 0 }
|
||||
property Weapon { def_id: int = 0, ammo: int = -1, cooldown: int = 0, phase: int = 0 }
|
||||
|
||||
property Projectile {
|
||||
color: int = 0xffffff, # drawn by the engine as a 3x3 square (Weapon.set_color)
|
||||
|
|
@ -92,20 +96,20 @@ function wpn_ensure() -> void {
|
|||
# gun. pattern: 0 single/aimed, 1 spread cone, 2 ring, 3 spiral. Returns the def id.
|
||||
@Namespace(Weapon) function weapon_def(name: pointer, fire_rate: int, damage: int, speed: int, spread: int, pellets: int, pattern: int) -> int {
|
||||
wpn_ensure()
|
||||
if wpn_n >= WPN_MAX { return 0 - 1 }
|
||||
if wpn_n >= WPN_MAX { return -1 }
|
||||
let id = wpn_n
|
||||
wpn_rate[id] = fire_rate; wpn_dmg[id] = damage; wpn_speed[id] = speed
|
||||
wpn_spread[id] = spread; wpn_pellets[id] = pellets; wpn_pattern[id] = pattern
|
||||
wpn_ttl[id] = 120
|
||||
wpn_color[id] = 0xffffff
|
||||
wpn_rate_base[id] = fire_rate
|
||||
wpn_n = wpn_n + 1
|
||||
wpn_n += 1
|
||||
Dict.set(wpn_reg, name, id)
|
||||
return id
|
||||
}
|
||||
@Namespace(Weapon) function weapon_id(name: pointer) -> int {
|
||||
wpn_ensure()
|
||||
return Dict.get_or(wpn_reg, name, 0 - 1)
|
||||
return Dict.get_or(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(id: int, frames: int) -> void {
|
||||
|
|
@ -180,10 +184,10 @@ function pr_set(e: int, name: pointer, v: int) -> void {
|
|||
# nearest entity with a Faction hostile to `fac`, by centre distance. -1 if none.
|
||||
function sh_nearest_enemy(self_e: int, fac: int, px: int, py: int) -> int {
|
||||
let PF = World.prop_id("Faction")
|
||||
if PF < 0 { return 0 - 1 }
|
||||
if PF < 0 { return -1 }
|
||||
let PP = World.prop_id("Position")
|
||||
if PP < 0 { return 0 - 1 }
|
||||
var best = 0 - 1
|
||||
if PP < 0 { return -1 }
|
||||
var best = -1
|
||||
var bestd = 0
|
||||
var e = World.query_next(PF, 0)
|
||||
while e >= 0 {
|
||||
|
|
@ -292,7 +296,7 @@ function melee_get(e: int, name: pointer) -> int { return sh_get("Melee", e, nam
|
|||
@Namespace(Melee) function melee_ready(e: int) -> bool { return melee_get(e, "cooldown") == 0 }
|
||||
@Namespace(Melee) function melee_active(e: int) -> bool { return melee_get(e, "frames_left") > 0 }
|
||||
@Namespace(Melee) function melee_swing(e: int) -> int {
|
||||
if melee_get(e, "cooldown") > 0 { return 0 - 1 }
|
||||
if melee_get(e, "cooldown") > 0 { return -1 }
|
||||
sh_set("Melee", e, "frames_left", melee_get(e, "frames"))
|
||||
sh_set("Melee", e, "cooldown", melee_get(e, "cooldown_frames"))
|
||||
let PF = World.prop_id("Faction")
|
||||
|
|
@ -316,7 +320,7 @@ function melee_get(e: int, name: pointer) -> int { return sh_get("Melee", e, nam
|
|||
let ny = pos_get(o, "y") + push.y
|
||||
if not Map.is_solid_at(nx, ny) { pos_set(o, "x", nx); pos_set(o, "y", ny) }
|
||||
emit MeleeHit(attacker: e, target: o)
|
||||
hits = hits + 1
|
||||
hits += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -405,23 +409,23 @@ function sh_half_w(e: int) -> int {
|
|||
let py = sh_cy(e)
|
||||
var target = td_get(e, "aim_angle") # default: keep current
|
||||
var have = 0
|
||||
if mode == 0 { # mouse / point
|
||||
if mode == AimMode.Mouse {
|
||||
let mx = Input.mouse_x()
|
||||
let my = Input.mouse_y()
|
||||
target = sh_angle_to(px, py, mx, my); have = 1
|
||||
}
|
||||
if mode == 1 { # right stick
|
||||
let ax = Input.pad_axis(0, 2)
|
||||
let ay = Input.pad_axis(0, 3)
|
||||
if mode == AimMode.RightStick {
|
||||
let ax = Input.pad_axis(0, STICK_RIGHT_X)
|
||||
let ay = Input.pad_axis(0, STICK_RIGHT_Y)
|
||||
if (ax != 0) or (ay != 0) { target = sh_dir_angle(ax, ay); have = 1 }
|
||||
}
|
||||
if mode == 2 { # move direction (from the intent)
|
||||
if mode == AimMode.MoveDirection { # from the movement intent
|
||||
let wx = td_get(e, "want_x")
|
||||
let wy = td_get(e, "want_y")
|
||||
if (wx != 0) or (wy != 0) { target = sh_dir_angle(wx, wy); have = 1 }
|
||||
}
|
||||
# mode 4 = manual: the game/AI owns aim_angle; the aim system leaves it alone.
|
||||
if mode == 3 { # nearest enemy (auto-aim)
|
||||
# AimMode.Manual: the game/AI owns aim_angle; the aim system leaves it alone.
|
||||
if mode == AimMode.NearestEnemy { # auto-aim
|
||||
let fac = Faction.id_of(e)
|
||||
let t = sh_nearest_enemy(e, fac, px, py)
|
||||
if t >= 0 { target = sh_angle_to(px, py, sh_cx(t), sh_cy(t)); have = 1 }
|
||||
|
|
@ -433,7 +437,7 @@ function sh_half_w(e: int) -> int {
|
|||
} else {
|
||||
let cur = td_get(e, "aim_angle")
|
||||
let d = sh_angle_diff(cur, target) # shortest signed arc, degrees
|
||||
if (d >= 0 - tr) and (d <= tr) { td_set(e, "aim_angle", target) }
|
||||
if (d >= -tr) and (d <= tr) { td_set(e, "aim_angle", target) }
|
||||
else { if d > 0 { td_set(e, "aim_angle", sh_wrap_deg(cur + tr)) } else { td_set(e, "aim_angle", sh_wrap_deg(cur - tr)) } }
|
||||
}
|
||||
}
|
||||
|
|
@ -471,21 +475,21 @@ function sh_dir_angle(dx: int, dy: int) -> int {
|
|||
}
|
||||
function sh_wrap_deg(d: int) -> int {
|
||||
var a = d
|
||||
while a < 0 { a = a + 360 }
|
||||
while a >= 360 { a = a - 360 }
|
||||
while a < 0 { a += 360 }
|
||||
while a >= 360 { a -= 360 }
|
||||
return a
|
||||
}
|
||||
function sh_angle_diff(from: int, to: int) -> int {
|
||||
var d = sh_wrap_deg(to) - sh_wrap_deg(from)
|
||||
while d > 180 { d = d - 360 }
|
||||
while d < 0 - 180 { d = d + 360 }
|
||||
while d > 180 { d -= 360 }
|
||||
while d < -180 { d += 360 }
|
||||
return d
|
||||
}
|
||||
|
||||
# spawn one projectile travelling at `deg` degrees from (px,py).
|
||||
function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed: int, dmg: int, ttl: int, pierce: int, homing: int) -> int {
|
||||
let m = World.model_id("Bullet")
|
||||
if m < 0 { return 0 - 1 }
|
||||
if m < 0 { return -1 }
|
||||
let b = World.spawn(m)
|
||||
pos_set(b, "x", px)
|
||||
pos_set(b, "y", py)
|
||||
|
|
@ -508,7 +512,7 @@ function sh_spawn_bullet(owner: int, fac: int, px: int, py: int, deg: int, speed
|
|||
}
|
||||
# announce it: a listener may veto (cancel -> despawn) or tweak the Projectile.
|
||||
pr_set(b, "color", wpn_fire_color)
|
||||
if emit ProjectileSpawned(proj: b, owner: owner, damage: dmg) != 0 { despawn b; return 0 - 1 }
|
||||
if emit ProjectileSpawned(proj: b, owner: owner, damage: dmg) != 0 { despawn b; return -1 }
|
||||
return b
|
||||
}
|
||||
var wpn_fire_color: int = 0xffffff # the colour of the weapon being fired (sh_fire)
|
||||
|
|
@ -554,17 +558,17 @@ function sh_fire(owner: int, id: int) -> void {
|
|||
wpn_fire_color = wpn_color[id]
|
||||
var pellets = pellets0; if pellets < 1 { pellets = 1 }
|
||||
emit WeaponFired(owner: owner, def_id: id)
|
||||
if pattern == 2 { # ring: pellets around 360
|
||||
if pattern == WeaponPattern.Ring { # pellets around 360
|
||||
var i = 0
|
||||
while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i = i + 1 }
|
||||
while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
|
||||
return
|
||||
}
|
||||
if pattern == 3 { # spiral: ring + a rotating phase
|
||||
if pattern == WeaponPattern.Spiral { # ring + a rotating phase
|
||||
let PW = World.prop_id("Weapon")
|
||||
let fph = World.field_id(PW, "phase")
|
||||
var ph = 0; if fph >= 0 { ph = World.get(owner, PW, fph) }
|
||||
var i = 0
|
||||
while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i = i + 1 }
|
||||
while i < pellets { sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + ph + i * 360 / pellets), speed, dmg, ttl, pierce, homing); i += 1 }
|
||||
if fph >= 0 { World.set(owner, PW, fph, sh_wrap_deg(ph + 15)) }
|
||||
return
|
||||
}
|
||||
|
|
@ -574,9 +578,9 @@ function sh_fire(owner: int, id: int) -> void {
|
|||
} else {
|
||||
var i = 0
|
||||
while i < pellets {
|
||||
let off = 0 - (spread / 2) + i * spread / (pellets - 1)
|
||||
let off = -(spread / 2) + i * spread / (pellets - 1)
|
||||
sh_spawn_bullet(owner, fac, px, py, sh_wrap_deg(base + off), speed, dmg, ttl, pierce, homing)
|
||||
i = i + 1
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -610,7 +614,7 @@ function sh_fire(owner: int, id: int) -> void {
|
|||
let cur = pr_get(e, "dir")
|
||||
let d = sh_angle_diff(cur, want)
|
||||
var na = cur
|
||||
if (d >= 0 - hom) and (d <= hom) { na = want }
|
||||
if (d >= -hom) and (d <= hom) { na = want }
|
||||
else { if d > 0 { na = sh_wrap_deg(cur + hom) } else { na = sh_wrap_deg(cur - hom) } }
|
||||
pr_set(e, "dir", na)
|
||||
let sp = pr_get(e, "speed")
|
||||
|
|
@ -688,7 +692,7 @@ event WaveCleared { spawner: int = 0, wave: int = 0 }
|
|||
var t = World.get(e, P, ft) - 1
|
||||
if t <= 0 {
|
||||
emit SpawnRequested(spawner: e, wave: World.get(e, P, fwave), index: spawned)
|
||||
spawned = spawned + 1
|
||||
spawned += 1
|
||||
World.set(e, P, fsp, spawned)
|
||||
World.set(e, P, ft, World.get(e, P, frate))
|
||||
if spawned >= budget { emit WaveCleared(spawner: e, wave: World.get(e, P, fwave)) }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue