#![no_std]
#![allow(clippy::too_many_arguments)]
extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use core::cell::RefCell;
use tishlang_runtime_gba::{get_prop, set_prop, value_call, Fixed, SingleCore, Value};
fn to_fixed(n: f64) -> Fixed {
Fixed::from_raw((n * 256.0) as i32)
}
fn from_fixed(f: Fixed) -> f64 {
f.to_raw() as f64 / 256.0
}
const C_TRANSFORM: u32 = 1 << 0;
const C_BODY: u32 = 1 << 1;
const C_SPRITE: u32 = 1 << 2;
const C_COLLIDER: u32 = 1 << 3;
const C_GRIDPOS: u32 = 1 << 4;
const C_ANIM: u32 = 1 << 5;
const C_WALK: u32 = 1 << 6;
const C_PLATFORMER: u32 = 1 << 7;
const C_HEALTH: u32 = 1 << 8;
const C_PATROL: u32 = 1 << 9;
const C_LIFE: u32 = 1 << 10;
const C_HURT: u32 = 1 << 11;
const C_MOVER: u32 = 1 << 12;
const C_TOPDOWN: u32 = 1 << 13;
const C_CHASE: u32 = 1 << 14;
const C_BLOCKER: u32 = 1 << 15;
const C_HOPPER: u32 = 1 << 16;
const C_JUMPER: u32 = 1 << 17;
const C_SHOOTER: u32 = 1 << 18;
const C_CHARGER: u32 = 1 << 19;
const C_GUARD: u32 = 1 << 20;
const C_DIRANIM: u32 = 1 << 21;
const C_CIRCLE: u32 = 1 << 22;
const C_DYNAMIC: u32 = 1 << 23;
const C_GRABBER: u32 = 1 << 24;
const C_TRAP: u32 = 1 << 25;
const C_FOLLOW: u32 = 1 << 26;
const C_BOOMERANG: u32 = 1 << 27;
const C_SEEK: u32 = 1 << 28;
const C_SOLDIER: u32 = 1 << 29;
const C_VISION: u32 = 1 << 30;
const C_SLEEP: u32 = 1 << 31;
pub const GUARD_MELEE: i32 = 1;
pub const GUARD_SHOT: i32 = 2;
pub const DMG_SWORD: i32 = 1;
pub const DMG_BOOMERANG: i32 = 2;
pub const DMG_ARROW: i32 = 4;
pub const DMG_BOMB: i32 = 8;
pub const DMG_MAGIC: i32 = 0x10;
pub const DMG_FIRE: i32 = 0x20;
const TILE: i32 = 16;
const CULL_MARGIN: i32 = 32;
const GRID_SPEED: i32 = 2;
const P_WALK: i32 = 320; const P_RUN: i32 = 576; const P_GRAVITY: i32 = 77; const P_JUMP: i32 = 1280; const P_TERMINAL: i32 = 1536; const P_COYOTE: i32 = 6; const P_JUMP_BUFFER: i32 = 6; const P_DROP: i32 = 8; const PF_INTERACT_PAD: i32 = 8;
const INVULN_FRAMES: i32 = 40;
const TD_WALK: i32 = 320; const TD_DIAG: i32 = 181; const TD_KNOCK: i32 = 1024; const TD_KNOCK_FRAMES: i32 = 8;
const TD_SNAP_TILE: u8 = 2;
#[derive(Clone, Copy, Default)]
struct Transform {
x: Fixed,
y: Fixed,
}
#[derive(Clone, Copy, Default)]
struct Body {
vx: Fixed,
vy: Fixed,
}
#[derive(Clone, Copy)]
struct SpriteRef {
handle: i32,
pub ox: i32,
pub oy: i32,
}
#[derive(Clone, Copy, Default)]
struct Collider {
w: Fixed,
h: Fixed,
}
#[derive(Clone, Copy, Default)]
struct BulletStyle {
sheet: i32,
frame: i32,
size: i32,
damage: i32,
target: i32,
tag: i32,
ttl: i32,
damage_type: i32,
}
#[derive(Clone, Copy, Default)]
struct GridPos {
col: i32,
row: i32,
moving: bool,
tx: Fixed,
ty: Fixed,
fx: i32,
fy: i32,
}
#[derive(Clone, Copy, Default)]
struct Anim {
from: i32,
len: i32,
speed: i32,
timer: i32,
cur: i32,
looping: bool,
playing: bool,
}
#[derive(Clone, Copy, Default)]
struct Walk {
cols: i32,
speed: i32,
timer: i32,
phase: bool,
}
fn last_cell(edge: Fixed) -> i32 {
((edge.to_raw() - 1) >> 8).div_euclid(TILE)
}
#[derive(Clone, Copy, Default)]
struct Platformer {
vx: Fixed,
vy: Fixed,
grounded: bool,
dir: i32, face: i32,
run: bool, coyote: i32, jump_buffer: i32, jumping: bool, drop: i32, blocked: bool, held: bool, walk_raw: i32,
run_raw: i32,
jump_raw: i32,
grav_raw: i32,
launch_raw: i32,
riding: bool,
carrier: i32,
}
#[derive(Clone, Copy, Default)]
struct Health {
hp: i32,
max: i32,
invuln: i32,
invuln_max: i32,
dead: bool,
}
#[derive(Clone, Copy)]
struct Patrol {
dir: i32,
flip_mode: i32,
flipped_for: i32,
}
impl Default for Patrol {
fn default() -> Self {
Patrol {
dir: -1,
flip_mode: 0,
flipped_for: 0,
}
}
}
#[derive(Clone, Copy, Default)]
struct Life {
ttl: i32,
offscreen: bool,
}
#[derive(Clone, Copy, Default)]
struct Dynamic {
restitution: i32,
friction: i32,
rest_v2: i32,
rank: u8,
asleep: u8,
last_hit: i32,
}
#[derive(Clone, Copy, Default)]
struct SurfaceDef {
ax: Fixed,
ay: Fixed,
friction: i32,
}
impl SurfaceDef {
const fn flat() -> Self {
SurfaceDef {
ax: Fixed::from_raw(0),
ay: Fixed::from_raw(0),
friction: 0,
}
}
}
struct Pool {
ent: Vec<i32>,
spr: Vec<i32>,
kind: Vec<i32>,
ox: i32,
oy: i32,
live: i32,
high: i32,
}
#[derive(Clone, Copy, Default)]
struct Hurt {
damage: i32,
target_tag: i32,
despawn_on_hit: bool,
stun: i32,
damage_type: i32,
}
#[derive(Clone, Copy, Default)]
struct Mover {
pattern: u8,
t: i32,
base_vy: Fixed,
amp: Fixed,
period: i32,
}
#[derive(Clone, Copy, Default)]
struct TopDown {
dx: i32,
dy: i32,
facing: i32,
moving: bool,
speed: i32, kx: Fixed,
ky: Fixed,
knock: i32,
snap_mode: u8,
snap_dx: i32,
snap_dy: i32,
snap_target_x: Fixed,
snap_target_y: Fixed,
}
#[derive(Clone, Copy, Default)]
struct DirAnim {
base: i32,
stride: i32,
frames: i32,
speed: i32,
}
#[derive(Clone, Copy, Default)]
struct Chase {
aggro: i32,
stride: i32,
flap: i32,
anim_speed: i32,
}
#[derive(Clone, Copy, Default)]
struct Seek {
field: i32,
arrive: i32,
stride: i32,
anim_speed: i32,
done: bool,
}
#[derive(Clone, Copy, Default)]
struct Soldier {
team: i32,
range: i32,
dmg: i32,
cooldown: i32,
timer: i32,
target: i32,
recheck: i32,
}
#[derive(Clone, Copy, Default)]
struct Vision {
radius: i32,
last_col: i32,
last_row: i32,
}
const MAX_FLOWS: usize = 6;
const FOG_UNSEEN: u8 = 0;
const FOG_EXPLORED: u8 = 1;
const FOG_VISIBLE: u8 = 2;
struct FlowField {
cols: i32,
rows: i32,
goal_col: i32,
goal_row: i32,
dist: Vec<u16>,
queue: Vec<i32>,
ready: bool,
}
impl FlowField {
const fn new() -> Self {
FlowField {
cols: 0,
rows: 0,
goal_col: -1,
goal_row: -1,
dist: Vec::new(),
queue: Vec::new(),
ready: false,
}
}
}
struct Fog {
cols: i32,
rows: i32,
state: Vec<u8>,
shown: Vec<u8>,
win: Vec<i32>,
on: bool,
}
impl Fog {
const fn new() -> Self {
Fog {
cols: 0,
rows: 0,
state: Vec::new(),
shown: Vec::new(),
win: Vec::new(),
on: false,
}
}
}
#[derive(Clone, Copy, Default)]
struct Wanderer {
turn_rate: i32,
turn_timer: i32,
want_shoot: i32,
last_x: i32,
last_y: i32,
home_rx: i32,
home_ry: i32,
}
#[derive(Clone, Copy, Default)]
struct Hopper {
stride: i32,
timer: i32,
state: i32,
start_x: Fixed,
start_y: Fixed,
dir_x: i32,
dir_y: i32,
}
#[derive(Clone, Copy, Default)]
struct Shooter {
interval: i32,
timer: i32,
speed: Fixed,
aimed: bool,
style: BulletStyle,
}
#[derive(Clone, Copy, Default)]
struct Charger {
speed: i32,
base: i32,
band: i32,
active: bool,
}
#[derive(Clone, Copy, Default)]
struct Grabber {
target_tag: i32,
}
#[derive(Clone, Copy, Default)]
struct Trap {
home_x: Fixed,
home_y: Fixed,
speed: i32,
base: i32,
band: i32,
active: bool,
}
const FOLLOW_PART: u8 = 0;
const FOLLOW_TRAIN: u8 = 1;
const FOLLOW_ORBIT: u8 = 2;
#[derive(Clone, Copy, Default)]
struct Follow {
kind: u8,
parent: i32,
radius: i32,
ox: Fixed,
oy: Fixed,
angle: i32,
}
#[derive(Clone, Copy, Default)]
struct Boomerang {
timer: i32,
owner: i32,
returning: bool,
}
const M2_NAI: u32 = 1 << 0;
const M2_PHASED: u32 = 1 << 1;
const M2_HIDDEN: u32 = 1 << 2;
const M2_PROXY: u32 = 1 << 3;
const M2_GATE: u32 = 1 << 4;
const M2_NOTE: u32 = 1 << 5;
const M2_CARRIER: u32 = 1 << 6;
const M2_WANDERER: u32 = 1 << 7;
const NAI_BOUNCE_G: i32 = 40;
#[derive(Clone, Copy, Default)]
struct Nai {
kind: u8,
state: u8,
timer: i32,
a: i32,
b: i32,
speed: i32,
step: i32,
dx: i32,
dy: i32,
aux: i32,
z: i32,
vz: i32,
style: BulletStyle,
}
#[derive(Clone, Copy, Default)]
struct Zx {
proxy: i32,
gate: i32,
code: i32,
}
#[derive(Clone, Copy, Default)]
struct Jumper {
timer: i32,
state: i32, dx: Fixed,
dy: Fixed,
z: Fixed,
dz: Fixed,
}
fn tri_fixed(t: i32, period: i32, amp: Fixed) -> Fixed {
if period <= 0 {
return Fixed::from_raw(0);
}
let half = (period / 2).max(1);
let p = t.rem_euclid(period);
let frac_raw = if p < half {
(p * 256) / half
} else {
((period - p) * 256) / half
};
let frac = Fixed::from_raw(frac_raw);
(frac * Fixed::from_raw(2 * 256) - Fixed::from_raw(256)) * amp
}
#[derive(Clone, Copy, Default)]
struct RoomCam {
enabled: bool,
room_w: i32,
room_h: i32,
cur_rx: i32,
cur_ry: i32,
transitioning: bool,
timer: i32,
dur: i32,
from_cam: (i32, i32),
to_cam: (i32, i32),
from_px: (i32, i32),
to_px: (i32, i32),
}
struct ComponentDef {
name: String,
start: Value,
update: Value,
on_collide: Value,
on_interact: Value,
on_death: Value,
tick: Value,
lean: bool,
}
struct BehaviourInstance {
def: usize,
data: Value,
started: bool,
}
struct World {
gen: Vec<u16>,
alive: Vec<bool>,
mask: Vec<u32>,
free: Vec<u32>,
pool_of: Vec<i32>,
pools: Vec<Pool>,
dynamic: Vec<Dynamic>,
surface: Vec<u8>,
surf: [SurfaceDef; 16],
buf_dyn: Vec<u32>,
transform: Vec<Transform>,
body: Vec<Body>,
sprite: Vec<SpriteRef>,
collider: Vec<Collider>,
gridpos: Vec<GridPos>,
anim: Vec<Anim>,
walk: Vec<Walk>,
platformer: Vec<Platformer>,
health: Vec<Health>,
patrol: Vec<Patrol>,
life: Vec<Life>,
hurt: Vec<Hurt>,
guard: Vec<i32>,
immune: Vec<i32>,
weak: Vec<i32>,
diranim: Vec<DirAnim>,
mover: Vec<Mover>,
topdown: Vec<TopDown>,
flows: [FlowField; MAX_FLOWS],
fog: Fog,
terr: Vec<i32>,
terr_cols: i32,
terr_rows: i32,
terr_shown: Vec<i32>,
terr_win: Vec<i32>,
fog_settled: bool,
chase: Vec<Chase>,
seek: Vec<Seek>,
soldier: Vec<Soldier>,
vision: Vec<Vision>,
hopper: Vec<Hopper>,
jumper: Vec<Jumper>,
tag: Vec<i32>,
shooter: Vec<Shooter>,
charger: Vec<Charger>,
grabber: Vec<Grabber>,
trap: Vec<Trap>,
follow: Vec<Follow>,
boomerang: Vec<Boomerang>,
mask2: Vec<u32>,
used: u32,
used2: u32,
nai: Vec<Nai>,
wanderer: Vec<Wanderer>,
zx: Vec<Zx>,
carr_prev: Vec<(Fixed, Fixed)>,
death_notes: Vec<i32>,
boomer_catches: i32,
stun: Vec<i32>,
lure: (i32, i32, i32),
ndata: Vec<[i32; 8]>,
behaviour: Vec<Option<BehaviourInstance>>,
defs: Vec<ComponentDef>,
grid_cols: i32,
grid_rows: i32,
solid: Vec<u8>,
oneway: Vec<u8>,
ladder: Vec<u8>,
grid_cells: usize,
camera_target: Option<i32>,
rng: u32,
room_cam: RoomCam,
cam_x: i32,
cam_y: i32,
bullet_style: BulletStyle,
arena_wrap: bool,
buf_updates: Vec<(Value, Value, i32)>,
buf_ticks: Vec<TickJob>,
buf_results: Vec<(i32, TickOut)>,
}
fn grid_bit_bytes(cells: usize) -> usize {
cells.div_ceil(8)
}
fn grid_bit(bits: &[u8], i: usize) -> bool {
let b = i / 8;
if b >= bits.len() {
return false;
}
(bits[b] & (1u8 << (i & 7))) != 0
}
fn grid_bit_set(bits: &mut [u8], i: usize, on: bool) {
let b = i / 8;
if b >= bits.len() {
return;
}
if on {
bits[b] |= 1u8 << (i & 7);
} else {
bits[b] &= !(1u8 << (i & 7));
}
}
fn encode(slot: u32, gen: u16) -> i32 {
((gen as i32) << 16) | (slot as i32 & 0xFFFF)
}
fn decode(e: i32) -> (u32, u16) {
((e & 0xFFFF) as u32, ((e >> 16) & 0x7FFF) as u16)
}
fn prop_num(obj: &Value, key: &str) -> f64 {
match get_prop(obj, key) {
Value::Number(x) => x,
_ => 0.0,
}
}
fn prop_truthy(obj: &Value, key: &str) -> bool {
match get_prop(obj, key) {
Value::Bool(b) => b,
Value::Number(x) => x != 0.0,
_ => false,
}
}
#[derive(Clone)]
struct TickJob {
cb: Value,
data: Value,
entity: i32,
x: i32,
y: i32,
grounded: bool,
blocked: bool,
vx: f64,
vy: f64,
platformer: bool,
body: bool,
lean: bool,
}
#[derive(Clone, Copy, Default)]
struct TickOut {
move_dir: i32,
jump: bool,
jump_cut: bool,
run: bool,
drop: bool,
flip: bool,
bounce: i32,
vx: f64,
vy: f64,
}
fn approach(v: &mut Fixed, target: Fixed, step: Fixed) -> bool {
if *v < target {
*v += step;
if *v >= target {
*v = target;
return true;
}
false
} else if *v > target {
*v -= step;
if *v <= target {
*v = target;
return true;
}
false
} else {
true
}
}
impl World {
const fn new() -> Self {
World {
gen: Vec::new(),
alive: Vec::new(),
mask: Vec::new(),
free: Vec::new(),
pool_of: Vec::new(),
pools: Vec::new(),
dynamic: Vec::new(),
surface: Vec::new(),
surf: [SurfaceDef::flat(); 16],
buf_dyn: Vec::new(),
transform: Vec::new(),
body: Vec::new(),
sprite: Vec::new(),
collider: Vec::new(),
gridpos: Vec::new(),
anim: Vec::new(),
walk: Vec::new(),
platformer: Vec::new(),
health: Vec::new(),
patrol: Vec::new(),
life: Vec::new(),
hurt: Vec::new(),
guard: Vec::new(),
immune: Vec::new(),
weak: Vec::new(),
diranim: Vec::new(),
mover: Vec::new(),
topdown: Vec::new(),
flows: [
FlowField::new(),
FlowField::new(),
FlowField::new(),
FlowField::new(),
FlowField::new(),
FlowField::new(),
],
fog: Fog::new(),
terr: Vec::new(),
terr_cols: 0,
terr_rows: 0,
terr_shown: Vec::new(),
terr_win: Vec::new(),
fog_settled: false,
chase: Vec::new(),
seek: Vec::new(),
soldier: Vec::new(),
vision: Vec::new(),
hopper: Vec::new(),
jumper: Vec::new(),
tag: Vec::new(),
shooter: Vec::new(),
charger: Vec::new(),
grabber: Vec::new(),
trap: Vec::new(),
follow: Vec::new(),
boomerang: Vec::new(),
mask2: Vec::new(),
used: 0,
used2: 0,
nai: Vec::new(),
wanderer: Vec::new(),
zx: Vec::new(),
carr_prev: Vec::new(),
death_notes: Vec::new(),
boomer_catches: 0,
stun: Vec::new(),
lure: (-1, 0, 0),
ndata: Vec::new(),
behaviour: Vec::new(),
defs: Vec::new(),
grid_cols: 0,
grid_rows: 0,
solid: Vec::new(),
oneway: Vec::new(),
ladder: Vec::new(),
grid_cells: 0,
camera_target: None,
rng: 123456789,
room_cam: RoomCam {
enabled: false,
room_w: 15,
room_h: 10,
cur_rx: 0,
cur_ry: 0,
transitioning: false,
timer: 0,
dur: 24,
from_cam: (0, 0),
to_cam: (0, 0),
from_px: (0, 0),
to_px: (0, 0),
},
cam_x: 0,
cam_y: 0,
bullet_style: BulletStyle {
sheet: 0,
frame: 0,
size: 6,
damage: 1,
target: 0,
tag: 0,
ttl: 240,
damage_type: 0,
},
arena_wrap: false,
buf_updates: Vec::new(),
buf_ticks: Vec::new(),
buf_results: Vec::new(),
}
}
fn spawn(&mut self) -> i32 {
let slot = if let Some(s) = self.free.pop() {
let s = s as usize;
self.alive[s] = true;
self.mask[s] = 0;
self.behaviour[s] = None;
self.tag[s] = 0; self.stun[s] = 0;
self.immune[s] = 0;
self.weak[s] = 0;
self.ndata[s] = [0; 8]; self.sprite[s].handle = -1;
self.sprite[s].ox = 0;
self.sprite[s].oy = 0;
self.pool_of[s] = -1;
self.mask2[s] = 0;
self.nai[s] = Nai::default();
self.wanderer[s] = Wanderer::default();
self.zx[s] = Zx::default();
self.carr_prev[s] = (Fixed::from_raw(0), Fixed::from_raw(0));
s
} else {
let s = self.gen.len();
self.gen.push(0);
self.alive.push(true);
self.mask.push(0);
self.pool_of.push(-1);
self.dynamic.push(Dynamic::default());
self.transform.push(Transform::default());
self.body.push(Body::default());
self.sprite.push(SpriteRef {
handle: -1,
ox: 0,
oy: 0,
});
self.collider.push(Collider::default());
self.gridpos.push(GridPos::default());
self.anim.push(Anim::default());
self.walk.push(Walk::default());
self.platformer.push(Platformer::default());
self.health.push(Health::default());
self.patrol.push(Patrol::default());
self.life.push(Life::default());
self.hurt.push(Hurt::default());
self.guard.push(0);
self.immune.push(0);
self.weak.push(0);
self.diranim.push(DirAnim::default());
self.mover.push(Mover::default());
self.topdown.push(TopDown::default());
self.chase.push(Chase::default());
self.seek.push(Seek::default());
self.soldier.push(Soldier::default());
self.vision.push(Vision::default());
self.hopper.push(Hopper::default());
self.jumper.push(Jumper::default());
self.tag.push(0);
self.shooter.push(Shooter::default());
self.charger.push(Charger::default());
self.grabber.push(Grabber::default());
self.trap.push(Trap::default());
self.follow.push(Follow::default());
self.boomerang.push(Boomerang::default());
self.mask2.push(0);
self.nai.push(Nai::default());
self.wanderer.push(Wanderer::default());
self.zx.push(Zx::default());
self.carr_prev
.push((Fixed::from_raw(0), Fixed::from_raw(0)));
self.stun.push(0);
self.ndata.push([0; 8]);
self.behaviour.push(None);
s
};
encode(slot as u32, self.gen[slot])
}
fn define_component(&mut self, name: String, config: &Value) -> usize {
let start = get_prop(config, "start");
let update = get_prop(config, "update");
let on_collide = get_prop(config, "onCollide");
let on_interact = get_prop(config, "onInteract");
let on_death = get_prop(config, "onDeath");
let tick = get_prop(config, "tick");
let lean = get_prop(config, "lean").is_truthy();
let idx = self.defs.len();
self.defs.push(ComponentDef {
name,
start,
update,
on_collide,
on_interact,
on_death,
tick,
lean,
});
idx
}
fn def_index_by_name(&self, name: &str) -> Option<usize> {
self.defs.iter().position(|d| d.name == name)
}
fn collect_behaviours(&mut self) {
self.buf_updates.clear();
for s in 0..self.alive.len() {
if !self.alive[s] || self.behaviour[s].is_none() {
continue;
}
if !self.is_active(s) {
continue;
}
let (def_idx, was_started) = {
let b = self.behaviour[s].as_ref().unwrap();
(b.def, b.started)
};
let has_start = !was_started && !matches!(self.defs[def_idx].start, Value::Null);
let has_update = !matches!(self.defs[def_idx].update, Value::Null);
if !was_started {
self.behaviour[s].as_mut().unwrap().started = true;
}
if !has_start && !has_update {
continue;
}
let data = self.behaviour[s].as_ref().unwrap().data.clone();
let entity = encode(s as u32, self.gen[s]);
if has_start {
self.buf_updates
.push((self.defs[def_idx].start.clone(), data.clone(), entity));
}
if has_update {
self.buf_updates
.push((self.defs[def_idx].update.clone(), data, entity));
}
}
}
fn collect_ticks(&mut self) {
self.buf_ticks.clear();
for s in 0..self.alive.len() {
if !self.alive[s] || self.behaviour[s].is_none() || !self.is_active(s) {
continue;
}
let def_idx = self.behaviour[s].as_ref().unwrap().def;
if matches!(self.defs[def_idx].tick, Value::Null) {
continue;
}
let cb = self.defs[def_idx].tick.clone();
let entity = encode(s as u32, self.gen[s]);
if self.defs[def_idx].lean {
self.buf_ticks.push(TickJob {
cb,
data: Value::Null,
entity,
x: 0,
y: 0,
grounded: false,
blocked: false,
vx: 0.0,
vy: 0.0,
platformer: false,
body: false,
lean: true,
});
continue;
}
let data = self.behaviour[s].as_ref().unwrap().data.clone();
let has_p = self.any(s, C_PLATFORMER);
let has_b = self.any(s, C_BODY);
self.buf_ticks.push(TickJob {
cb,
data,
entity,
x: self.transform[s].x.floor(),
y: self.transform[s].y.floor(),
grounded: has_p && self.platformer[s].grounded,
blocked: has_p && self.platformer[s].blocked,
vx: if has_b {
from_fixed(self.body[s].vx)
} else {
0.0
},
vy: if has_b {
from_fixed(self.body[s].vy)
} else {
0.0
},
platformer: has_p,
body: has_b,
lean: false,
});
}
}
fn apply_tick(&mut self, entity: i32, out: &TickOut) {
let Some(s) = self.slot_of(entity) else {
return;
};
if self.any(s, C_PLATFORMER) {
let p = &mut self.platformer[s];
p.dir = out.move_dir.signum();
p.run = out.run;
if out.drop {
p.drop = P_DROP;
}
if out.jump {
p.jump_buffer = P_JUMP_BUFFER;
}
if out.jump_cut && p.jumping && p.vy.to_raw() < 0 {
p.vy = Fixed::from_raw(p.vy.to_raw() / 2);
p.jumping = false;
}
if out.bounce > 0 {
p.vy = Fixed::from_raw(-out.bounce.abs() * 256);
p.grounded = false;
p.jumping = false;
}
}
if self.any(s, C_BODY) && !self.any(s, C_PLATFORMER) {
self.body[s].vx = to_fixed(out.vx);
self.body[s].vy = to_fixed(out.vy);
}
if self.any(s, C_SPRITE) {
let h = self.sprite[s].handle;
if h >= 0 {
tish_agb::native_sprite_set_flip(h, out.flip);
}
}
}
fn slot_of(&self, e: i32) -> Option<usize> {
let (slot, g) = decode(e);
let s = slot as usize;
if s < self.alive.len() && self.alive[s] && self.gen[s] == g {
Some(s)
} else {
None
}
}
fn cvar(&self, e: i32, k: usize) -> i32 {
self.slot_of(e).map(|s| self.ndata[s][k & 7]).unwrap_or(0)
}
fn set_cvar(&mut self, e: i32, k: usize, v: i32) {
if let Some(s) = self.slot_of(e) {
self.ndata[s][k & 7] = v;
}
}
fn despawn(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
if self.any(s, C_SPRITE) && self.sprite[s].handle >= 0 {
tish_agb::native_sprite_destroy(self.sprite[s].handle);
self.sprite[s].handle = -1;
}
self.alive[s] = false;
self.mask[s] = 0;
self.mask2[s] = 0;
self.behaviour[s] = None;
self.pool_of[s] = -1;
self.gen[s] = self.gen[s].wrapping_add(1);
self.free.push(s as u32);
}
}
fn reset_entity(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
let handle = self.sprite[s].handle;
if handle >= 0 {
tish_agb::native_sprite_set_visible(handle, false);
}
self.mask[s] = if handle >= 0 { C_SPRITE } else { 0 };
self.used |= C_SPRITE;
self.behaviour[s] = None;
self.tag[s] = 0;
self.stun[s] = 0;
self.immune[s] = 0;
self.weak[s] = 0;
self.ndata[s] = [0; 8];
self.transform[s] = Transform::default();
self.body[s] = Body::default();
self.collider[s] = Collider::default();
self.gridpos[s] = GridPos::default();
self.anim[s] = Anim::default();
self.walk[s] = Walk::default();
self.platformer[s] = Platformer::default();
self.health[s] = Health::default();
self.patrol[s] = Patrol::default();
self.life[s] = Life::default();
self.hurt[s] = Hurt::default();
self.guard[s] = 0;
self.diranim[s] = DirAnim::default();
self.mover[s] = Mover::default();
self.topdown[s] = TopDown::default();
self.chase[s] = Chase::default();
self.seek[s] = Seek::default();
self.soldier[s] = Soldier::default();
self.vision[s] = Vision::default();
self.hopper[s] = Hopper::default();
self.jumper[s] = Jumper::default();
self.shooter[s] = Shooter::default();
self.charger[s] = Charger::default();
self.grabber[s] = Grabber::default();
self.trap[s] = Trap::default();
self.follow[s] = Follow::default();
self.boomerang[s] = Boomerang::default();
self.mask2[s] = 0;
self.nai[s] = Nai::default();
self.wanderer[s] = Wanderer::default();
self.zx[s] = Zx::default();
self.carr_prev[s] = (Fixed::from_raw(0), Fixed::from_raw(0));
self.dynamic[s] = Dynamic::default();
self.sprite[s] = SpriteRef {
handle,
ox: 0,
oy: 0,
};
}
}
fn surface_at(&self, col: i32, row: i32) -> u8 {
if self.surface.is_empty()
|| col < 0
|| row < 0
|| col >= self.grid_cols
|| row >= self.grid_rows
{
return 0;
}
let i = (row * self.grid_cols + col) as usize;
let b = self.surface[i >> 1];
if i & 1 == 0 {
b & 0x0f
} else {
b >> 4
}
}
fn grid_set_surface(&mut self, col: i32, row: i32, id: i32) {
if col < 0 || row < 0 || col >= self.grid_cols || row >= self.grid_rows {
return;
}
if self.surface.is_empty() {
self.surface = alloc::vec![0u8; self.grid_cells.div_ceil(2)];
}
let i = (row * self.grid_cols + col) as usize;
let v = (id.clamp(0, 15) as u8) & 0x0f;
let b = &mut self.surface[i >> 1];
if i & 1 == 0 {
*b = (*b & 0xf0) | v
} else {
*b = (*b & 0x0f) | (v << 4)
}
}
fn substeps(vx: Fixed, vy: Fixed) -> i32 {
const D_MAX_STEP: i32 = 2048; let m = vx.to_raw().abs().max(vy.to_raw().abs());
if m <= D_MAX_STEP {
1
} else if m <= D_MAX_STEP << 1 {
2
} else if m <= D_MAX_STEP << 2 {
4
} else {
8
}
}
fn dynamic_system(&mut self) {
let n = self.alive.len();
for s in 0..n {
if !self.alive[s]
|| self.mask[s] & C_SLEEP != 0
|| !self.has(s, C_DYNAMIC | C_TRANSFORM | C_BODY)
|| self.dynamic[s].asleep != 0
{
continue;
}
let (cx, cy) = self.center_of(s);
let id = self.surface_at(cx.to_raw() / (TILE << 8), cy.to_raw() / (TILE << 8)) as usize;
let sd = self.surf[id];
let f = if sd.friction != 0 {
sd.friction
} else {
self.dynamic[s].friction
};
let mut b = self.body[s];
b.vx += sd.ax;
b.vy += sd.ay;
b.vx = Fixed::from_raw((b.vx.to_raw() * f) >> 8);
b.vy = Fixed::from_raw((b.vy.to_raw() * f) >> 8);
self.body[s] = b;
}
let walled = self.grid_cols > 0;
for s in 0..n {
if !self.alive[s]
|| !self.has(s, C_DYNAMIC | C_TRANSFORM | C_BODY | C_COLLIDER)
|| self.dynamic[s].asleep != 0
{
continue;
}
let steps = Self::substeps(self.body[s].vx, self.body[s].vy);
let rest = self.dynamic[s].restitution;
for _ in 0..steps {
let (vx, vy) = (self.body[s].vx / steps, self.body[s].vy / steps);
let c = self.collider[s];
let t = self.transform[s];
let nx = t.x + vx;
if walled && self.box_hits_solid(nx, t.y, c.w, c.h) {
self.body[s].vx = Fixed::from_raw(-((self.body[s].vx.to_raw() * rest) >> 8));
} else {
self.transform[s].x = nx;
}
let t = self.transform[s];
let ny = t.y + vy;
if walled && self.box_hits_solid(t.x, ny, c.w, c.h) {
self.body[s].vy = Fixed::from_raw(-((self.body[s].vy.to_raw() * rest) >> 8));
} else {
self.transform[s].y = ny;
}
}
}
for s in 0..n {
if !self.alive[s] || !self.has(s, C_DYNAMIC | C_BODY) || self.dynamic[s].asleep != 0 {
continue;
}
let (vx, vy) = (self.body[s].vx.to_raw(), self.body[s].vy.to_raw());
let v2 = ((vx >> 4) * (vx >> 4)) + ((vy >> 4) * (vy >> 4));
if v2 <= self.dynamic[s].rest_v2 {
self.body[s].vx = Fixed::from_raw(0);
self.body[s].vy = Fixed::from_raw(0);
self.dynamic[s].asleep = 1;
}
}
self.buf_dyn.clear();
for s in 0..n {
if self.alive[s] && self.has(s, C_DYNAMIC | C_CIRCLE | C_TRANSFORM | C_COLLIDER) {
self.buf_dyn.push(s as u32);
}
}
for i in 1..self.buf_dyn.len() {
let mut j = i;
while j > 0 {
let (a, b) = (self.buf_dyn[j - 1] as usize, self.buf_dyn[j] as usize);
if self.transform[a].x.to_raw() <= self.transform[b].x.to_raw() {
break;
}
self.buf_dyn.swap(j - 1, j);
j -= 1;
}
}
let m = self.buf_dyn.len();
for i in 0..m {
let a = self.buf_dyn[i] as usize;
let ra = self.collider[a].w.to_raw() >> 1;
for k in (i + 1)..m {
let b = self.buf_dyn[k] as usize;
let rb = self.collider[b].w.to_raw() >> 1;
let (ca, cb) = (self.center_of(a), self.center_of(b));
let dx = cb.0.to_raw() - ca.0.to_raw();
if dx > ra + rb {
break;
}
let dy = cb.1.to_raw() - ca.1.to_raw();
let rsum = ra + rb;
let (pdx, pdy, prs) = (dx >> 8, dy >> 8, rsum >> 8);
if pdx * pdx + pdy * pdy > prs * prs {
continue;
}
if pdx == 0 && pdy == 0 {
continue;
} self.resolve_pair(a, b, dx, dy, rsum);
}
}
}
fn resolve_pair(&mut self, a: usize, b: usize, dx: i32, dy: i32, rsum: i32) {
let d2 = ((dx >> 8) * (dx >> 8) + (dy >> 8) * (dy >> 8)).max(1);
let mut len = 1i32;
while len * len < d2 {
len += 1;
} let len_raw = (len << 8).max(1);
let pen = rsum - len_raw;
if pen <= 0 {
return;
}
let nx = (dx << 8) / len_raw;
let ny = (dy << 8) / len_raw;
let (rka, rkb) = (self.dynamic[a].rank, self.dynamic[b].rank);
let (sa, sb) = if rka == rkb {
(pen >> 1, pen >> 1)
} else if rka < rkb {
(pen, 0)
} else {
(0, pen)
};
if sa > 0 {
let old = self.transform[a];
self.transform[a].x -= Fixed::from_raw((nx * sa) >> 8);
self.transform[a].y -= Fixed::from_raw((ny * sa) >> 8);
let c = self.collider[a];
if self.grid_cols > 0
&& self.box_hits_solid(self.transform[a].x, self.transform[a].y, c.w, c.h)
{
self.transform[a] = old;
}
self.dynamic[a].asleep = 0;
}
if sb > 0 {
let old = self.transform[b];
self.transform[b].x += Fixed::from_raw((nx * sb) >> 8);
self.transform[b].y += Fixed::from_raw((ny * sb) >> 8);
let c = self.collider[b];
if self.grid_cols > 0
&& self.box_hits_solid(self.transform[b].x, self.transform[b].y, c.w, c.h)
{
self.transform[b] = old;
}
self.dynamic[b].asleep = 0;
}
let rest = self.dynamic[a].restitution.min(self.dynamic[b].restitution);
let van = (self.body[a].vx.to_raw() * nx + self.body[a].vy.to_raw() * ny) >> 8;
let vbn = (self.body[b].vx.to_raw() * nx + self.body[b].vy.to_raw() * ny) >> 8;
let approaching = van - vbn;
if approaching > 0 {
let j = (approaching * (256 + rest)) >> 8;
let (ja, jb) = if rka == rkb {
(j >> 1, j >> 1)
} else if rka < rkb {
(j, 0)
} else {
(0, j)
};
if ja > 0 {
self.body[a].vx -= Fixed::from_raw((nx * ja) >> 8);
self.body[a].vy -= Fixed::from_raw((ny * ja) >> 8);
self.dynamic[a].asleep = 0;
self.dynamic[a].last_hit = encode(b as u32, self.gen[b]);
}
if jb > 0 {
self.body[b].vx += Fixed::from_raw((nx * jb) >> 8);
self.body[b].vy += Fixed::from_raw((ny * jb) >> 8);
self.dynamic[b].asleep = 0;
self.dynamic[b].last_hit = encode(a as u32, self.gen[a]);
}
}
}
fn pool_new(&mut self, count: i32, sheet: i32, ox: i32, oy: i32) -> i32 {
if count <= 0 {
return -1;
}
let mut pool = Pool {
ent: Vec::new(),
spr: Vec::new(),
kind: Vec::new(),
ox,
oy,
live: 0,
high: 0,
};
let p = self.pools.len() as i32;
for i in 0..count {
let e = self.spawn();
let s = self.slot_of(e).unwrap();
self.pool_of[s] = (p << 16) | i;
let h = if sheet >= 0 {
let h = tish_agb::sprite_new_typed(sheet);
if h >= 0 {
tish_agb::native_sprite_set_visible(h, false);
self.sprite[s] = SpriteRef { handle: h, ox, oy };
self.mask[s] |= C_SPRITE;
self.used |= C_SPRITE;
}
h
} else {
-1
};
pool.ent.push(e);
pool.spr.push(h);
pool.kind.push(-1);
}
self.pools.push(pool);
p
}
fn pool_arm(&mut self, p: i32, slot: i32, kind: i32, ttl: i32) -> i32 {
let pi = match self.pools.get(p as usize) {
Some(_) => p as usize,
None => return -1,
};
let n = self.pools[pi].ent.len();
let idx = if slot >= 0 {
let i = slot as usize;
if i >= n || self.pools[pi].kind[i] >= 0 {
return -1;
}
i
} else {
match (0..n).find(|&i| self.pools[pi].kind[i] < 0) {
Some(i) => i,
None if slot == -2 => {
let mut best = 0usize;
let mut best_ttl = i32::MAX;
for i in 0..n {
let t = self
.slot_of(self.pools[pi].ent[i])
.map(|s| self.life[s].ttl)
.unwrap_or(0);
if t < best_ttl {
best_ttl = t;
best = i;
}
}
self.pool_retire(p, best as i32);
best
}
None => return -1,
}
};
let e = self.pools[pi].ent[idx];
let s = match self.slot_of(e) {
Some(s) => s,
None => return -1,
};
self.reset_entity(e);
let (ox, oy) = (self.pools[pi].ox, self.pools[pi].oy);
self.sprite[s].ox = ox;
self.sprite[s].oy = oy;
let h = self.pools[pi].spr[idx];
if h >= 0 {
tish_agb::native_sprite_set_visible(h, true);
}
if ttl > 0 {
self.life[s] = Life {
ttl,
offscreen: false,
};
self.mask[s] |= C_LIFE;
self.used |= C_LIFE;
}
self.pools[pi].kind[idx] = kind;
self.pools[pi].live += 1;
if self.pools[pi].live > self.pools[pi].high {
self.pools[pi].high = self.pools[pi].live;
}
e
}
fn pool_retire(&mut self, p: i32, slot: i32) {
let pi = p as usize;
let i = slot as usize;
if pi >= self.pools.len() || i >= self.pools[pi].ent.len() || self.pools[pi].kind[i] < 0 {
return;
}
let e = self.pools[pi].ent[i];
self.reset_entity(e);
if let Some(s) = self.slot_of(e) {
self.mask[s] &= !C_LIFE;
}
if self.pools[pi].spr[i] >= 0 {
tish_agb::native_sprite_set_visible(self.pools[pi].spr[i], false);
}
self.pools[pi].kind[i] = -1;
self.pools[pi].live -= 1;
}
fn pool_retire_packed(&mut self, packed: i32) {
self.pool_retire(packed >> 16, packed & 0xffff);
}
fn pool_clear(&mut self, p: i32) {
let pi = p as usize;
if pi >= self.pools.len() {
return;
}
for i in 0..self.pools[pi].ent.len() as i32 {
self.pool_retire(p, i);
}
}
fn pool_get(&self, p: i32, slot: i32, field: i32) -> i32 {
let pi = p as usize;
let i = slot as usize;
if pi >= self.pools.len() || i >= self.pools[pi].ent.len() {
return -1;
}
match field {
0 => self.pools[pi].kind[i],
1 => self
.slot_of(self.pools[pi].ent[i])
.map(|s| self.life[s].ttl)
.unwrap_or(0),
2 => self.pools[pi].ent[i],
3 => self.pools[pi].spr[i],
_ => -1,
}
}
fn pool_stat(&self, p: i32, field: i32) -> i32 {
let pi = p as usize;
if pi >= self.pools.len() {
return -1;
}
match field {
0 => self.pools[pi].ent.len() as i32,
1 => self.pools[pi].live,
2 => self.pools[pi].high,
_ => -1,
}
}
fn clear_world(&mut self) {
self.used = 0;
self.used2 = 0;
for s in 0..self.alive.len() {
if self.alive[s] {
if self.any(s, C_SPRITE) && self.sprite[s].handle >= 0 {
tish_agb::native_sprite_destroy(self.sprite[s].handle);
self.sprite[s].handle = -1;
}
self.alive[s] = false;
self.mask[s] = 0;
self.behaviour[s] = None;
self.pool_of[s] = -1;
self.gen[s] = self.gen[s].wrapping_add(1);
self.free.push(s as u32);
}
}
self.pools.clear();
self.grid_cols = 0;
self.grid_rows = 0;
self.grid_cells = 0;
self.solid.clear();
self.oneway.clear();
self.ladder.clear();
self.surface.clear();
}
fn has(&self, s: usize, bits: u32) -> bool {
self.mask[s] & bits == bits
}
fn any(&self, s: usize, bits: u32) -> bool {
(self.mask[s] & bits) != 0
}
fn movement_system(&mut self) {
for s in 0..self.alive.len() {
if self.alive[s]
&& self.mask[s] & C_SLEEP == 0
&& self.has(s, C_TRANSFORM | C_BODY)
&& !self.any(s, C_DYNAMIC)
{
self.transform[s].x += self.body[s].vx;
self.transform[s].y += self.body[s].vy;
}
}
}
fn wrap_system(&mut self) {
if !self.arena_wrap {
return;
}
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_TRANSFORM) {
continue;
}
let (w, h) = if self.has(s, C_COLLIDER) {
(self.collider[s].w, self.collider[s].h)
} else {
(Fixed::from_raw(TILE << 8), Fixed::from_raw(TILE << 8))
};
let span_x = Fixed::from_raw(240 << 8) + w;
let span_y = Fixed::from_raw(160 << 8) + h;
let t = &mut self.transform[s];
while t.x >= Fixed::from_raw(240 << 8) {
t.x -= span_x;
}
while t.x < -w {
t.x += span_x;
}
while t.y >= Fixed::from_raw(160 << 8) {
t.y -= span_y;
}
while t.y < -h {
t.y += span_y;
}
}
}
fn set_arena_wrap(&mut self, on: bool) {
self.arena_wrap = on;
}
fn set_platformer(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
self.platformer[s] = Platformer {
face: 1,
..Platformer::default()
};
self.mask[s] |= C_PLATFORMER;
self.used |= C_PLATFORMER;
}
}
fn platformer_walk(&mut self, e: i32, dir: i32) {
if self.input_locked(e) {
return;
}
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
let d = dir.signum();
self.platformer[s].dir = d;
if d != 0 {
self.platformer[s].face = d;
}
}
}
fn platformer_run(&mut self, e: i32, on: bool) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].run = on && !self.input_locked(e);
}
}
fn platformer_jump(&mut self, e: i32) {
if self.input_locked(e) {
return;
}
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].jump_buffer = P_JUMP_BUFFER;
}
}
fn platformer_jump_release(&mut self, e: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
let p = &mut self.platformer[s];
if p.jumping && p.vy.to_raw() < 0 {
p.vy = Fixed::from_raw(p.vy.to_raw() / 2);
p.jumping = false;
}
}
}
fn platformer_drop(&mut self, e: i32) {
if self.input_locked(e) {
return;
}
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].drop = P_DROP;
}
}
fn platformer_bounce(&mut self, e: i32, vel: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].vy = Fixed::from_raw(-vel.abs() * 256);
self.platformer[s].grounded = false;
self.platformer[s].jumping = false;
}
}
fn platformer_set_vy(&mut self, e: i32, vy_raw: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].vy = Fixed::from_raw(vy_raw);
self.platformer[s].jumping = false;
}
}
fn platformer_set_speed(&mut self, e: i32, walk_raw: i32, run_raw: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].walk_raw = walk_raw;
self.platformer[s].run_raw = run_raw;
}
}
fn platformer_set_physics(&mut self, e: i32, jump_raw: i32, grav_raw: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
self.platformer[s].jump_raw = jump_raw;
self.platformer[s].grav_raw = grav_raw;
}
}
fn platformer_launch(&mut self, e: i32, vx_raw: i32, vy_raw: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_PLATFORMER)) {
let p = &mut self.platformer[s];
p.launch_raw = vx_raw;
p.vy = Fixed::from_raw(vy_raw);
p.held = false;
p.riding = false;
p.grounded = false;
}
}
fn input_locked(&self, e: i32) -> bool {
self.room_cam.enabled && self.room_cam.transitioning && self.camera_target == Some(e)
}
fn box_hits_solid(&self, x: Fixed, y: Fixed, w: Fixed, h: Fixed) -> bool {
let left = x.floor();
let top = y.floor();
let right = ((x + w).to_raw() - 1) >> 8;
let bottom = ((y + h).to_raw() - 1) >> 8;
if right < left || bottom < top {
return false;
}
let (c0, c1) = (left.div_euclid(TILE), right.div_euclid(TILE));
let (r0, r1) = (top.div_euclid(TILE), bottom.div_euclid(TILE));
let mut r = r0;
while r <= r1 {
let mut c = c0;
while c <= c1 {
if self.is_solid(c, r) {
return true;
}
c += 1;
}
r += 1;
}
false
}
fn on_floor(&self, x: Fixed, y: Fixed, w: Fixed, h: Fixed, oneway: bool) -> bool {
let row = (y + h).floor().div_euclid(TILE);
let c0 = x.floor().div_euclid(TILE);
let c1 = (x + w).floor().saturating_sub(1).div_euclid(TILE);
let mut c = c0;
while c <= c1 {
if self.is_solid(c, row) || (oneway && self.is_oneway(c, row)) {
return true;
}
c += 1;
}
false
}
fn oneway_floor(
&self,
x: Fixed,
prev_y: Fixed,
new_y: Fixed,
w: Fixed,
h: Fixed,
) -> Option<i32> {
let prev_bottom = (prev_y + h).floor();
let new_bottom = (new_y + h).floor();
let row = (new_bottom - 1).div_euclid(TILE);
let top = row * TILE;
if prev_bottom <= top && new_bottom > top {
let (c0, c1) = (
x.floor().div_euclid(TILE),
(x + w).floor().saturating_sub(1).div_euclid(TILE),
);
let mut c = c0;
while c <= c1 {
if self.is_oneway(c, row) {
return Some(row);
}
c += 1;
}
}
None
}
fn platformer_system(&mut self) {
let mut carriers = [0usize; 16];
let mut ncar = 0usize;
for s in 0..self.alive.len() {
if self.alive[s]
&& self.mask2[s] & M2_CARRIER != 0
&& self.has(s, C_TRANSFORM | C_COLLIDER)
&& ncar < carriers.len()
{
carriers[ncar] = s;
ncar += 1;
}
}
let frozen = if self.room_cam.enabled && self.room_cam.transitioning {
self.camera_target.and_then(|e| self.slot_of(e))
} else {
None
};
for i in 0..ncar {
let s = carriers[i];
if !self.has(s, C_PLATFORMER) || Some(s) == frozen || !self.is_active(s) {
continue;
}
self.platformer_integrate(s, &carriers[..ncar]);
}
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_PLATFORMER | C_TRANSFORM | C_COLLIDER) {
continue;
}
if self.mask2[s] & M2_CARRIER != 0 {
continue;
}
if Some(s) == frozen || !self.is_active(s) {
continue; }
self.platformer_integrate(s, &carriers[..ncar]);
}
for &s in carriers.iter().take(ncar) {
self.carr_prev[s] = (self.transform[s].x, self.transform[s].y);
}
}
fn platformer_integrate(&mut self, s: usize, carriers: &[usize]) {
let term = Fixed::from_raw(P_TERMINAL);
let zero = Fixed::from_raw(0);
if self.platformer[s].held {
let p = &mut self.platformer[s];
p.vx = Fixed::from_raw(0);
p.vy = Fixed::from_raw(0);
p.grounded = false;
p.blocked = false;
p.jump_buffer = 0;
p.coyote = 0;
p.jumping = false;
p.riding = false;
p.launch_raw = 0;
return;
}
if self.platformer[s].riding {
let car = self.platformer[s].carrier;
match self
.slot_of(car)
.filter(|&c| self.mask2[c] & M2_CARRIER != 0 && self.same_room(s, c))
{
Some(c) => {
let (px, py) = self.carr_prev[c];
let dx = self.transform[c].x - px;
let dy = self.transform[c].y - py;
self.transform[s].x += dx;
self.transform[s].y += dy;
}
None => self.platformer[s].riding = false,
}
}
let (w, h) = (self.collider[s].w, self.collider[s].h);
let p = self.platformer[s];
let g = Fixed::from_raw(if p.grav_raw != 0 {
p.grav_raw
} else {
P_GRAVITY
});
let was_grounded = p.grounded;
let mut coyote = if was_grounded {
P_COYOTE
} else {
(p.coyote - 1).max(0)
};
let mut jump_buffer = p.jump_buffer;
let mut jumping = p.jumping;
let speed = if p.run {
if p.run_raw != 0 {
p.run_raw
} else {
P_RUN
}
} else if p.walk_raw != 0 {
p.walk_raw
} else {
P_WALK
};
let mut launch = p.launch_raw;
let mut vx = if launch != 0 {
Fixed::from_raw(launch)
} else {
Fixed::from_raw(p.dir.signum() * speed)
};
let mut vy = p.vy;
if jump_buffer > 0 && (was_grounded || coyote > 0) {
vy = Fixed::from_raw(-(if p.jump_raw != 0 { p.jump_raw } else { P_JUMP }));
jumping = true;
jump_buffer = 0;
coyote = 0;
}
jump_buffer = (jump_buffer - 1).max(0);
let drop = (p.drop - 1).max(0);
vy += g;
if vy > term {
vy = term;
}
if vy.to_raw() >= 0 {
jumping = false;
}
let (x, y) = (self.transform[s].x, self.transform[s].y);
let nx = x + vx;
let rx = if vx > zero && self.box_hits_solid(nx, y, w, h) {
let col = last_cell(nx + w);
vx = zero;
launch = 0;
Fixed::from_raw(col * TILE * 256) - w
} else if vx < zero && self.box_hits_solid(nx, y, w, h) {
let col = nx.floor().div_euclid(TILE);
vx = zero;
launch = 0;
Fixed::from_raw((col + 1) * TILE * 256)
} else {
nx
};
let ny = y + vy;
let mut grounded = false;
let mut riding = false;
let mut carrier_id = p.carrier;
let mut ry = if vy > zero {
if self.box_hits_solid(rx, ny, w, h) {
let row = last_cell(ny + h);
vy = zero;
grounded = true;
Fixed::from_raw(row * TILE * 256) - h
} else if drop == 0 {
match self.oneway_floor(rx, y, ny, w, h) {
Some(row) => {
vy = zero;
grounded = true;
Fixed::from_raw(row * TILE * 256) - h
}
None => match self.carrier_land(s, carriers, rx, y, ny, w, h) {
Some((top, id)) => {
vy = zero;
grounded = true;
riding = true;
carrier_id = id;
top - h
}
None => ny,
},
}
} else {
ny
}
} else if vy < zero && self.box_hits_solid(rx, ny, w, h) {
let row = ny.floor().div_euclid(TILE);
vy = zero;
Fixed::from_raw((row + 1) * TILE * 256)
} else {
ny
};
if !grounded && vy >= zero && self.on_floor(rx, ry, w, h, drop == 0) {
grounded = true;
vy = zero;
let floor_row = (ry + h).floor().div_euclid(TILE);
ry = Fixed::from_raw(floor_row * TILE * 256) - h;
}
if grounded {
launch = 0; }
self.transform[s].x = rx;
self.transform[s].y = ry;
let blocked = p.dir != 0 && vx.to_raw() == 0;
let p = &mut self.platformer[s];
p.vx = vx;
p.vy = vy;
p.grounded = grounded;
p.coyote = coyote;
p.jump_buffer = jump_buffer;
p.jumping = jumping;
p.drop = drop;
p.blocked = blocked;
p.launch_raw = launch;
p.riding = riding;
p.carrier = carrier_id;
}
fn carrier_land(
&self,
s: usize,
carriers: &[usize],
x: Fixed,
prev_y: Fixed,
new_y: Fixed,
w: Fixed,
h: Fixed,
) -> Option<(Fixed, i32)> {
let prev_bottom = prev_y + h;
let new_bottom = new_y + h;
let tol = Fixed::from_raw(4 * 256);
let mut best: Option<(Fixed, i32)> = None;
for &c in carriers {
if c == s || !self.alive[c] || !self.same_room(s, c) {
continue;
}
let cx = self.transform[c].x;
let cw = self.collider[c].w;
if x + w <= cx || x >= cx + cw {
continue;
}
let top = self.transform[c].y;
if prev_bottom <= top + tol && new_bottom > top {
let better = match best {
Some((bt, _)) => top < bt,
None => true,
};
if better {
best = Some((top, encode(c as u32, self.gen[c])));
}
}
}
best
}
fn set_topdown(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
self.topdown[s] = TopDown {
speed: TD_WALK,
..TopDown::default()
};
self.mask[s] |= C_TOPDOWN;
self.used |= C_TOPDOWN;
}
}
fn set_blocker(&mut self, e: i32) {
if let Some(s) = self
.slot_of(e)
.filter(|&s| self.has(s, C_TRANSFORM | C_COLLIDER))
{
self.mask[s] |= C_BLOCKER;
self.used |= C_BLOCKER;
}
}
fn set_carrier(&mut self, e: i32) {
if let Some(s) = self
.slot_of(e)
.filter(|&s| self.has(s, C_TRANSFORM | C_COLLIDER))
{
self.mask2[s] |= M2_CARRIER;
self.used2 |= M2_CARRIER;
self.carr_prev[s] = (self.transform[s].x, self.transform[s].y);
}
}
fn first_blocker_hit(
&self,
mover: usize,
x: Fixed,
y: Fixed,
w: Fixed,
h: Fixed,
) -> Option<(Fixed, Fixed, Fixed, Fixed)> {
for b in 0..self.alive.len() {
if b == mover
|| !self.alive[b]
|| !self.has(b, C_BLOCKER | C_TRANSFORM | C_COLLIDER)
|| !self.is_active(b)
{
continue;
}
if !self.same_room(mover, b) {
continue;
}
let bx = self.transform[b].x;
let by = self.transform[b].y;
let bw = self.collider[b].w;
let bh = self.collider[b].h;
if x < bx + bw && bx < x + w && y < by + bh && by < y + h {
return Some((bx, by, bw, bh));
}
}
None
}
fn set_shooter(&mut self, e: i32, interval: i32, speed: Fixed, aimed: bool) {
let style = self.bullet_style;
if let Some(s) = self.slot_of(e) {
self.rng = self.rng.wrapping_mul(1664525).wrapping_add(1013904223);
let jitter = ((self.rng >> 16) as i32).rem_euclid(interval.max(1));
self.shooter[s] = Shooter {
interval: interval.max(1),
timer: jitter,
speed,
aimed,
style,
};
self.mask[s] |= C_SHOOTER;
self.used |= C_SHOOTER;
}
}
fn shooter_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(ts) = self.slot_of(target) else {
return;
};
let (ptx, pty) = self.center_of(ts);
let mut shots: [(Fixed, Fixed, Fixed, Fixed, BulletStyle); 8] = [(
Fixed::from_raw(0),
Fixed::from_raw(0),
Fixed::from_raw(0),
Fixed::from_raw(0),
BulletStyle::default(),
); 8];
let mut n = 0usize;
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_SHOOTER | C_TRANSFORM) || !self.is_active(s) {
continue;
}
if self.stun[s] > 0 || !self.same_room(s, ts) {
continue;
}
self.shooter[s].timer -= 1;
if self.shooter[s].timer > 0 {
continue;
}
let sh = self.shooter[s];
self.shooter[s].timer = sh.interval;
if n == shots.len() {
continue;
}
let (cx, cy) = self.center_of(s);
let (ux, uy) = if sh.aimed {
let (dx, dy) = (ptx - cx, pty - cy);
let len = (dx * dx + dy * dy).sqrt();
if len.to_raw() == 0 {
(Fixed::from_raw(0), Fixed::from_raw(256))
} else {
(dx / len, dy / len)
}
} else {
match self.topdown[s].facing {
1 => (Fixed::from_raw(0), Fixed::from_raw(-256)),
2 => (Fixed::from_raw(-256), Fixed::from_raw(0)),
3 => (Fixed::from_raw(256), Fixed::from_raw(0)),
_ => (Fixed::from_raw(0), Fixed::from_raw(256)),
}
};
let muzzle = Fixed::from_raw(12 * 256);
shots[n] = (
cx + ux * muzzle,
cy + uy * muzzle,
ux * sh.speed,
uy * sh.speed,
sh.style,
);
n += 1;
}
for &(x, y, vx, vy, style) in shots.iter().take(n) {
let save = self.bullet_style;
self.bullet_style = style;
self.fire_bullet(x, y, vx, vy);
self.bullet_style = save;
}
}
fn center_of(&self, s: usize) -> (Fixed, Fixed) {
if self.has(s, C_COLLIDER) {
(
self.transform[s].x + self.collider[s].w / 2,
self.transform[s].y + self.collider[s].h / 2,
)
} else {
(self.transform[s].x, self.transform[s].y)
}
}
fn set_charger(&mut self, e: i32, speed: i32, band: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
self.charger[s] = Charger {
speed: speed.max(1) * 256,
base: self.topdown[s].speed,
band: band.max(1),
active: false,
};
self.mask[s] |= C_CHARGER;
self.used |= C_CHARGER;
}
}
fn charger_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(ts) = self.slot_of(target) else {
return;
};
let (px, py) = (self.transform[ts].x.floor(), self.transform[ts].y.floor());
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_CHARGER | C_TOPDOWN) || !self.is_active(s) {
continue;
}
let c = self.charger[s];
let lined = if self.stun[s] > 0 || !self.same_room(s, ts) {
None
} else {
let dx = px - self.transform[s].x.floor();
let dy = py - self.transform[s].y.floor();
if dy.abs() <= c.band && dx != 0 {
Some((dx.signum(), 0))
} else if dx.abs() <= c.band && dy != 0 {
Some((0, dy.signum()))
} else {
None
}
};
match lined {
Some((mx, my)) => {
if !self.charger[s].active {
self.charger[s].active = true;
self.charger[s].base = self.topdown[s].speed;
self.topdown[s].speed = c.speed;
}
self.topdown[s].dx = mx;
self.topdown[s].dy = my;
self.topdown[s].facing = if mx < 0 {
2
} else if mx > 0 {
3
} else if my < 0 {
1
} else {
0
};
}
None => {
if self.charger[s].active {
self.charger[s].active = false;
self.topdown[s].speed = c.base;
}
}
}
}
}
fn set_weakness(&mut self, e: i32, mask: i32) {
if let Some(s) = self.slot_of(e) {
self.weak[s] = mask;
}
}
fn set_grabber(&mut self, e: i32, target_tag: i32) {
if let Some(s) = self.slot_of(e) {
self.grabber[s] = Grabber { target_tag };
self.mask[s] |= C_GRABBER;
self.used |= C_GRABBER;
}
}
fn grabber_system(&mut self) {
const GRAB_STUN: i32 = 24;
let n = self.alive.len();
let mut stuns: Vec<(usize, i32)> = Vec::new();
for s in 0..n {
if !self.alive[s]
|| !self.has(s, C_GRABBER | C_TRANSFORM | C_COLLIDER)
|| !self.is_active(s)
{
continue;
}
let want = self.grabber[s].target_tag;
for t in 0..n {
if t == s || !self.alive[t] || self.tag[t] != want {
continue;
}
if !self.has(t, C_TRANSFORM | C_COLLIDER) || !self.slots_overlap(s, t) {
continue;
}
if !self.same_room(s, t) {
continue;
}
stuns.push((t, GRAB_STUN));
}
}
for (t, frames) in stuns {
self.stun[t] = self.stun[t].max(frames);
}
}
fn set_trap(&mut self, e: i32) {
if let Some(s) = self
.slot_of(e)
.filter(|&s| self.has(s, C_TOPDOWN | C_TRANSFORM))
{
let base = self.topdown[s].speed;
self.trap[s] = Trap {
home_x: self.transform[s].x,
home_y: self.transform[s].y,
speed: 4 * 256,
base,
band: 8,
active: false,
};
self.mask[s] |= C_TRAP;
self.used |= C_TRAP;
}
}
fn trap_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(ts) = self.slot_of(target) else {
return;
};
let (px, py) = (self.transform[ts].x.floor(), self.transform[ts].y.floor());
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_TRAP | C_TOPDOWN) || !self.is_active(s) {
continue;
}
let c = self.trap[s];
let lined = if self.stun[s] > 0 || !self.same_room(s, ts) {
None
} else {
let dx = px - self.transform[s].x.floor();
let dy = py - self.transform[s].y.floor();
if dy.abs() <= c.band && dx != 0 {
Some((dx.signum(), 0))
} else if dx.abs() <= c.band && dy != 0 {
Some((0, dy.signum()))
} else {
None
}
};
match lined {
Some((mx, my)) => {
if !self.trap[s].active {
self.trap[s].active = true;
self.trap[s].base = self.topdown[s].speed;
self.topdown[s].speed = c.speed;
}
self.topdown[s].dx = mx;
self.topdown[s].dy = my;
self.topdown[s].facing = if mx < 0 {
2
} else if mx > 0 {
3
} else if my < 0 {
1
} else {
0
};
}
None => {
if self.trap[s].active {
self.trap[s].active = false;
self.topdown[s].speed = c.base;
}
let dx = c.home_x.floor() - self.transform[s].x.floor();
let dy = c.home_y.floor() - self.transform[s].y.floor();
if dx.abs() <= 1 && dy.abs() <= 1 {
self.transform[s].x = c.home_x;
self.transform[s].y = c.home_y;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
} else if dx.abs() >= dy.abs() {
self.topdown[s].dx = dx.signum();
self.topdown[s].dy = 0;
} else {
self.topdown[s].dx = 0;
self.topdown[s].dy = dy.signum();
}
}
}
}
}
fn set_follow(&mut self, e: i32, kind: u8, parent: i32, radius: i32) {
let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TRANSFORM)) else {
return;
};
let (ox, oy) = match self.slot_of(parent).filter(|&p| self.has(p, C_TRANSFORM)) {
Some(p) => (
self.transform[s].x - self.transform[p].x,
self.transform[s].y - self.transform[p].y,
),
None => (Fixed::from_raw(0), Fixed::from_raw(0)),
};
self.follow[s] = Follow {
kind,
parent,
radius: radius.max(0),
ox,
oy,
angle: 0,
};
self.mask[s] |= C_FOLLOW;
self.used |= C_FOLLOW;
}
fn follow_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_FOLLOW | C_TRANSFORM)
{
continue;
}
let f = self.follow[s];
let Some(p) = self
.slot_of(f.parent)
.filter(|&p| self.alive[p] && self.has(p, C_TRANSFORM))
else {
continue;
};
let (px, py) = (self.transform[p].x, self.transform[p].y);
match f.kind {
FOLLOW_ORBIT => {
let rev = Fixed::from_raw(f.angle & 255);
let r = Fixed::from_raw(f.radius * 256);
self.transform[s].x = px + rev.cos() * r;
self.transform[s].y = py + rev.sin() * r;
self.follow[s].angle = f.angle.wrapping_add(2);
}
_ => {
self.transform[s].x = px + f.ox;
self.transform[s].y = py + f.oy;
}
}
}
}
fn set_boomerang(&mut self, e: i32, return_frames: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_BODY)) {
let owner = self.camera_target.unwrap_or(-1);
self.boomerang[s] = Boomerang {
timer: return_frames.max(1),
owner,
returning: false,
};
self.mask[s] |= C_BOOMERANG;
self.used |= C_BOOMERANG;
}
}
fn boomerang_system(&mut self) {
let mut caught: [i32; 4] = [-1; 4];
let mut nc = 0usize;
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_BOOMERANG | C_BODY) || !self.is_active(s) {
continue;
}
if !self.boomerang[s].returning {
self.boomerang[s].timer -= 1;
if self.boomerang[s].timer > 0 {
continue;
}
self.boomerang[s].returning = true;
self.body[s].vx = -self.body[s].vx;
self.body[s].vy = -self.body[s].vy;
}
let owner = self.boomerang[s].owner;
let Some(os) = self
.slot_of(owner)
.filter(|&os| self.alive[os] && self.has(os, C_TRANSFORM))
else {
continue;
};
if !self.has(s, C_TRANSFORM) {
continue;
}
let (ox, oy) = (self.transform[os].x, self.transform[os].y);
let (ex, ey) = (self.transform[s].x, self.transform[s].y);
let (dx, dy) = (ox - ex, oy - ey);
if dx.to_raw().abs() < 10 * 256 && dy.to_raw().abs() < 10 * 256 {
if nc < caught.len() {
caught[nc] = encode(s as u32, self.gen[s]);
nc += 1;
self.boomer_catches += 1;
}
continue;
}
let speed = {
let vx = self.body[s].vx.to_raw();
let vy = self.body[s].vy.to_raw();
vx.abs().max(vy.abs()).max(256)
};
let adx = dx.to_raw().abs().max(1);
let ady = dy.to_raw().abs().max(1);
let dom = adx.max(ady);
self.body[s].vx = Fixed::from_raw((dx.to_raw() * speed) / dom);
self.body[s].vy = Fixed::from_raw((dy.to_raw() * speed) / dom);
}
for &id in caught.iter().take(nc) {
self.despawn(id);
}
}
fn set_lure(&mut self, e: i32, radius: i32, frames: i32) {
self.lure = (e, radius, frames);
}
fn lure_point(&mut self) -> Option<(i32, i32)> {
let (e, _r, f) = self.lure;
if e < 0 || f <= 0 {
return None;
}
self.lure.2 = f - 1;
let s = self.slot_of(e)?;
if !self.alive[s] || !self.has(s, C_TRANSFORM) {
self.lure = (-1, 0, 0);
return None;
}
Some((self.transform[s].x.floor(), self.transform[s].y.floor()))
}
fn lured_to(&self, s: usize, lp: Option<(i32, i32)>) -> Option<(i32, i32)> {
let (lx, ly) = lp?;
if self.lure.0 >= 0 && self.slot_of(self.lure.0) == Some(s) {
return None; }
let d = (lx - self.transform[s].x.floor()).abs() + (ly - self.transform[s].y.floor()).abs();
if d <= self.lure.1 {
Some((lx, ly))
} else {
None
}
}
fn rnd16(&mut self) -> u32 {
self.rng = self.rng.wrapping_mul(1664525).wrapping_add(1013904223);
self.rng >> 16
}
fn nai_head(&mut self, s: usize, mx: i32, my: i32) {
self.nai[s].dx = mx;
self.nai[s].dy = my;
self.topdown[s].dx = mx;
self.topdown[s].dy = my;
if mx < 0 {
self.topdown[s].facing = 2;
} else if mx > 0 {
self.topdown[s].facing = 3;
} else if my < 0 {
self.topdown[s].facing = 1;
} else if my > 0 {
self.topdown[s].facing = 0;
}
}
fn nai_pick_dir8(&mut self, s: usize) {
const DIRS: [(i32, i32); 8] = [
(0, 1),
(0, -1),
(1, 0),
(-1, 0),
(1, 1),
(1, -1),
(-1, 1),
(-1, -1),
];
let (mx, my) = DIRS[(self.rnd16() & 7) as usize];
self.nai_head(s, mx, my);
}
fn nai_install(&mut self, e: i32, kind: u8, a: i32, b: i32, speed: i32) -> Option<usize> {
let s = self.slot_of(e)?;
let a = a.max(1);
let jitter = (self.rnd16() as i32).rem_euclid(a);
self.nai[s] = Nai {
kind,
state: 0,
timer: a - (jitter >> 1).min(a - 1),
a,
b: b.max(1),
speed,
..Nai::default()
};
self.mask2[s] |= M2_NAI;
self.used2 |= M2_NAI;
Some(s)
}
fn set_ambusher(&mut self, e: i32, hide: i32, surface: i32, speed: i32) {
if let Some(s) = self.nai_install(e, 1, hide, surface, speed) {
if !self.has(s, C_TOPDOWN) {
self.mask2[s] &= !M2_NAI;
return;
}
self.mask2[s] |= M2_PHASED | M2_HIDDEN;
self.used2 |= M2_PHASED | M2_HIDDEN;
}
}
fn set_drifter(&mut self, e: i32, rest: i32, fly: i32, speed: i32) {
if let Some(s) = self.nai_install(e, 2, rest, fly.max(4), speed) {
if !self.has(s, C_TOPDOWN) {
self.mask2[s] &= !M2_NAI;
return;
}
self.nai[s].step = (speed * 4 / fly.max(4)).max(1);
}
}
fn set_flicker_caster(&mut self, e: i32, hide: i32, vis: i32, shot_speed: Fixed) {
let style = self.bullet_style;
if let Some(s) = self.nai_install(e, 3, hide, vis.max(16), shot_speed.to_raw()) {
if !self.has(s, C_TRANSFORM) {
self.mask2[s] &= !M2_NAI;
return;
}
self.nai[s].style = style;
self.mask2[s] |= M2_PHASED | M2_HIDDEN;
self.used2 |= M2_PHASED | M2_HIDDEN;
}
}
fn set_ricochet(&mut self, e: i32, speed: i32) {
if let Some(s) = self.nai_install(e, 5, 1, 1, speed) {
if !self.has(s, C_TOPDOWN) {
self.mask2[s] &= !M2_NAI;
return;
}
self.nai[s].dx = if s & 1 == 0 { 1 } else { -1 };
self.nai[s].dy = if s & 2 == 0 { 1 } else { -1 };
self.nai[s].a = self.transform[s].x.to_raw() - 1;
self.nai[s].b = self.transform[s].y.to_raw() - 1;
}
}
fn set_bouncer(&mut self, e: i32, rest: i32, hop: i32, speed: i32) {
if let Some(s) = self.nai_install(e, 4, rest, hop.max(8), speed) {
if !self.has(s, C_TOPDOWN) {
self.mask2[s] &= !M2_NAI;
return;
}
self.nai[s].aux = self.sprite[s].oy;
}
}
fn wanderer_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(ts) = self.slot_of(target) else {
return;
};
let tx = self.transform[ts].x.floor();
let ty = self.transform[ts].y.floor();
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask2[s] & M2_WANDERER == 0 || !self.has(s, C_TOPDOWN) {
continue;
}
let mut at_edge = false;
if self.room_cam.enabled {
let (rw, rh) = (self.room_cam.room_w * TILE, self.room_cam.room_h * TILE);
let (left, top) = (self.wanderer[s].home_rx * rw, self.wanderer[s].home_ry * rh);
let (cw, ch) = if self.has(s, C_COLLIDER) {
(self.collider[s].w.floor(), self.collider[s].h.floor())
} else {
(0, 0)
};
let (right, bottom) = (left + rw - cw, top + rh - ch);
let (x, y) = (self.transform[s].x.floor(), self.transform[s].y.floor());
if x < left {
self.transform[s].x = Fixed::from_raw(left << 8);
at_edge = true;
} else if x > right {
self.transform[s].x = Fixed::from_raw(right << 8);
at_edge = true;
}
if y < top {
self.transform[s].y = Fixed::from_raw(top << 8);
at_edge = true;
} else if y > bottom {
self.transform[s].y = Fixed::from_raw(bottom << 8);
at_edge = true;
}
}
if !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
if !self.same_room(s, ts) {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
if self.wanderer[s].turn_timer > 0 {
self.wanderer[s].turn_timer -= 1;
}
if self.topdown[s].knock > 0 {
continue;
}
let px = self.transform[s].x.floor();
let py = self.transform[s].y.floor();
let aligned = (px & 0xF) == 0 && (py & 0xF) == 0;
let (rx_now, ry_now) = (self.transform[s].x.to_raw(), self.transform[s].y.to_raw());
let blocked = rx_now == self.wanderer[s].last_x && ry_now == self.wanderer[s].last_y;
self.wanderer[s].last_x = rx_now;
self.wanderer[s].last_y = ry_now;
let stuck = blocked || at_edge;
if stuck {
self.wanderer[s].turn_timer = 0;
}
if self.topdown[s].speed != 0 && (aligned || stuck) {
let r = (self.rnd16() & 0xFF) as i32;
if r > self.wanderer[s].turn_rate {
self.wanderer_turn_if_time(s, px, py, tx, ty);
} else if (px - tx).abs() < 9 {
self.wanderer_turn_y(s, py, ty);
} else if (py - ty).abs() < 9 {
self.wanderer_turn_x(s, px, tx);
} else {
self.wanderer_turn_if_time(s, px, py, tx, ty);
}
}
let (dx, dy) = match self.topdown[s].facing {
1 => (0, -1),
2 => (-1, 0),
3 => (1, 0),
_ => (0, 1),
};
self.topdown[s].dx = dx;
self.topdown[s].dy = dy;
}
}
fn wanderer_turn_if_time(&mut self, s: usize, px: i32, py: i32, tx: i32, ty: i32) {
self.wanderer[s].want_shoot = 0;
if self.wanderer[s].turn_timer != 0 {
return;
}
if self.topdown[s].facing <= 1 {
self.wanderer_turn_x(s, px, tx);
} else {
self.wanderer_turn_y(s, py, ty);
}
}
fn wanderer_turn_x(&mut self, s: usize, px: i32, tx: i32) {
self.topdown[s].facing = if tx < px { 2 } else { 3 };
let t = (self.rnd16() & 0xFF) as i32;
self.wanderer[s].turn_timer = t;
self.wanderer[s].want_shoot = 1;
}
fn wanderer_turn_y(&mut self, s: usize, py: i32, ty: i32) {
self.topdown[s].facing = if ty < py { 1 } else { 0 };
let t = (self.rnd16() & 0xFF) as i32;
self.wanderer[s].turn_timer = t;
self.wanderer[s].want_shoot = 1;
}
fn wanderer_wants_shot(&self, s: usize) -> bool {
self.wanderer[s].want_shoot != 0
}
fn nai_system(&mut self) {
let ts = self.camera_target.and_then(|t| self.slot_of(t));
let mut shots: [(Fixed, Fixed, Fixed, Fixed, BulletStyle); 4] = [(
Fixed::from_raw(0),
Fixed::from_raw(0),
Fixed::from_raw(0),
Fixed::from_raw(0),
BulletStyle::default(),
); 4];
let mut nsh = 0usize;
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask2[s] & M2_NAI == 0 || !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
continue;
}
let near = ts.filter(|&t| self.same_room(s, t));
let z = self.nai[s];
match z.kind {
1 => {
if z.state == 0 {
if let Some(t) = near {
let dx = self.transform[t].x.floor() - self.transform[s].x.floor();
let dy = self.transform[t].y.floor() - self.transform[s].y.floor();
let (mx, my) = if dx.abs() >= dy.abs() {
(dx.signum(), 0)
} else {
(0, dy.signum())
};
self.topdown[s].speed = z.speed;
self.nai_head(s, mx, my);
} else {
self.nai_head(s, 0, 0);
}
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 1;
self.nai[s].timer = z.b;
self.mask2[s] &= !(M2_PHASED | M2_HIDDEN);
self.nai_head(s, 0, 0);
}
} else {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 0;
self.nai[s].timer = z.a;
self.mask2[s] |= M2_PHASED | M2_HIDDEN;
self.used2 |= M2_PHASED | M2_HIDDEN;
}
}
}
2 => {
if z.state == 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 1;
self.nai[s].timer = z.b;
self.nai[s].aux = z.step; self.mask2[s] |= M2_PHASED;
self.used2 |= M2_PHASED;
self.nai_pick_dir8(s);
}
} else {
let quarter = (z.b >> 2).max(1);
let elapsed = z.b - z.timer;
let cur = if elapsed < quarter {
(z.aux + z.step).min(z.speed)
} else if z.timer < quarter {
(z.aux - z.step).max(z.step)
} else {
z.speed
};
self.nai[s].aux = cur;
self.topdown[s].speed = cur;
if z.timer & 31 == 0 {
self.nai_pick_dir8(s);
} else {
self.topdown[s].dx = z.dx;
self.topdown[s].dy = z.dy;
}
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 0;
self.nai[s].timer = z.a;
self.mask2[s] &= !M2_PHASED;
self.nai_head(s, 0, 0);
}
}
}
3 => {
if z.state == 0 {
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
if let Some(t) = near {
let pc = self.transform[t].x.floor() / TILE;
let pr = self.transform[t].y.floor() / TILE;
for _ in 0..8 {
let r = self.rnd16();
let dc = ((r & 7) as i32) - 3;
let dr = (((r >> 3) & 7) as i32) - 3;
if dc.abs() + dr.abs() < 2 {
continue; }
let (c, rw) = (pc + dc, pr + dr);
if !self.is_solid(c, rw) {
self.transform[s].x = Fixed::from_raw(c * TILE * 256);
self.transform[s].y = Fixed::from_raw(rw * TILE * 256);
break;
}
}
}
self.nai[s].state = 1;
self.nai[s].timer = z.b;
self.nai[s].aux = 0; }
} else {
let elapsed = z.b - z.timer;
if elapsed < 12 {
self.mask2[s] |= M2_PHASED;
self.used2 |= M2_PHASED;
if elapsed & 2 == 0 {
self.mask2[s] |= M2_HIDDEN;
self.used2 |= M2_HIDDEN;
} else {
self.mask2[s] &= !M2_HIDDEN;
}
} else {
self.mask2[s] &= !(M2_PHASED | M2_HIDDEN);
}
if elapsed == z.b >> 1 && self.nai[s].aux == 0 && nsh < shots.len() {
if let Some(t) = near {
self.nai[s].aux = 1;
let (ptx, pty) = self.center_of(t);
let (cx, cy) = self.center_of(s);
let (dx, dy) = (ptx - cx, pty - cy);
let len = (dx * dx + dy * dy).sqrt();
let (ux, uy) = if len.to_raw() == 0 {
(Fixed::from_raw(0), Fixed::from_raw(256))
} else {
(dx / len, dy / len)
};
let muzzle = Fixed::from_raw(12 * 256);
let spd = Fixed::from_raw(z.speed);
shots[nsh] = (
cx + ux * muzzle,
cy + uy * muzzle,
ux * spd,
uy * spd,
z.style,
);
nsh += 1;
}
}
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 0;
self.nai[s].timer = z.a;
self.mask2[s] |= M2_PHASED | M2_HIDDEN;
self.used2 |= M2_PHASED | M2_HIDDEN;
}
}
}
4 => {
if z.state == 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 1;
self.nai[s].timer = z.b;
let r = self.rnd16();
if let (true, Some(t)) = (r & 1 == 0, near) {
let dx = self.transform[t].x.floor() - self.transform[s].x.floor();
let dy = self.transform[t].y.floor() - self.transform[s].y.floor();
let (mx, my) = if dx.abs() >= dy.abs() {
(dx.signum(), 0)
} else {
(0, dy.signum())
};
self.nai_head(s, mx, my);
} else {
self.nai_pick_dir8(s);
}
self.topdown[s].speed = z.speed;
self.nai[s].z = 0;
self.nai[s].vz = (NAI_BOUNCE_G * z.b) >> 1;
}
} else {
self.topdown[s].dx = z.dx;
self.topdown[s].dy = z.dy;
self.nai[s].z = (self.nai[s].z + self.nai[s].vz).max(0);
self.nai[s].vz -= NAI_BOUNCE_G;
if self.has(s, C_SPRITE) {
self.sprite[s].oy = z.aux - (self.nai[s].z >> 8);
}
self.nai[s].timer -= 1;
if self.nai[s].timer <= 0 {
self.nai[s].state = 0;
self.nai[s].timer = z.a;
self.nai[s].z = 0;
if self.has(s, C_SPRITE) {
self.sprite[s].oy = z.aux;
}
self.nai_head(s, 0, 0);
}
}
}
5 => {
let px = self.transform[s].x.to_raw();
let py = self.transform[s].y.to_raw();
if px == z.a && z.dx != 0 {
self.nai[s].dx = -z.dx;
}
if py == z.b && z.dy != 0 {
self.nai[s].dy = -z.dy;
}
self.nai[s].a = px;
self.nai[s].b = py;
self.topdown[s].speed = z.speed;
self.topdown[s].dx = self.nai[s].dx;
self.topdown[s].dy = self.nai[s].dy;
}
_ => {}
}
if self.has(s, C_SPRITE) {
let h = self.sprite[s].handle;
if h >= 0 {
tish_agb::native_sprite_set_visible(h, self.mask2[s] & M2_HIDDEN == 0);
}
}
}
for &(x, y, vx, vy, style) in shots.iter().take(nsh) {
let save = self.bullet_style;
self.bullet_style = style;
self.fire_bullet(x, y, vx, vy);
self.bullet_style = save;
}
}
fn set_hit_proxy(&mut self, e: i32, target: i32) {
if let Some(s) = self.slot_of(e) {
if target < 0 {
self.mask2[s] &= !M2_PROXY;
} else {
self.zx[s].proxy = target;
self.mask2[s] |= M2_PROXY;
self.used2 |= M2_PROXY;
}
}
}
fn set_vuln_gate(&mut self, e: i32, open: i32) {
if let Some(s) = self.slot_of(e) {
self.zx[s].gate = open;
self.mask2[s] |= M2_GATE;
self.used2 |= M2_GATE;
}
}
fn set_death_note(&mut self, e: i32, code: i32) {
if let Some(s) = self.slot_of(e) {
self.zx[s].code = code;
self.mask2[s] |= M2_NOTE;
self.used2 |= M2_NOTE;
}
}
fn set_phased(&mut self, e: i32, on: bool) {
if let Some(s) = self.slot_of(e) {
if on {
self.mask2[s] |= M2_PHASED;
self.used2 |= M2_PHASED;
} else {
self.mask2[s] &= !M2_PHASED;
}
}
}
fn detach_part(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
self.mask[s] &= !C_FOLLOW;
}
}
fn set_chase(&mut self, e: i32, aggro: i32, stride: i32, flap: i32, anim_speed: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
self.chase[s] = Chase {
aggro,
stride,
flap: flap.max(1),
anim_speed: anim_speed.max(1),
};
self.mask[s] |= C_CHASE;
self.used |= C_CHASE;
}
}
fn chase_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(ts) = self.slot_of(target) else {
return;
};
let ptx = self.transform[ts].x.floor();
let pty = self.transform[ts].y.floor();
let lp = self.lure_point();
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_CHASE | C_TOPDOWN) || !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
let (tx, ty) = self.lured_to(s, lp).unwrap_or((ptx, pty));
let c = self.chase[s];
let dx = tx - self.transform[s].x.floor();
let dy = ty - self.transform[s].y.floor();
let (mut mx, mut my) = (0, 0);
let same_room = match self.camera_target.and_then(|e| self.slot_of(e)) {
Some(ts) => self.same_room(s, ts),
None => true,
};
let dist = dx.abs() + dy.abs();
if same_room && dist < c.aggro && dist > 12 {
if dx.abs() > 6 {
mx = dx.signum();
}
if dy.abs() > 6 {
my = dy.signum();
}
}
let owns_frame = self.has(s, C_DIRANIM);
let td = &mut self.topdown[s];
td.dx = mx;
td.dy = my;
if c.stride > 0 {
if mx < 0 {
td.facing = 2;
} else if mx > 0 {
td.facing = 3;
} else if my < 0 {
td.facing = 1;
} else if my > 0 {
td.facing = 0;
}
if !owns_frame {
let base = td.facing * c.stride;
let moving = mx != 0 || my != 0;
let e = encode(s as u32, self.gen[s]);
if moving {
self.anim_play(e, base + 1, 4, c.anim_speed, true);
} else {
self.anim_play(e, base, 1, 1, false);
}
}
} else if !owns_frame {
let e = encode(s as u32, self.gen[s]);
self.anim_play(e, 0, c.flap, c.anim_speed, true);
}
}
}
fn flow_ensure(&mut self, id: usize) -> bool {
if id >= MAX_FLOWS || self.grid_cols <= 0 || self.grid_rows <= 0 {
return false;
}
let cells = (self.grid_cols * self.grid_rows) as usize;
let f = &mut self.flows[id];
if f.cols != self.grid_cols || f.rows != self.grid_rows {
f.cols = self.grid_cols;
f.rows = self.grid_rows;
f.dist = alloc::vec![u16::MAX; cells];
f.queue = Vec::with_capacity(cells);
f.goal_col = -1;
f.goal_row = -1;
f.ready = false;
}
true
}
fn flow_goal(&mut self, id: usize, col: i32, row: i32) {
if !self.flow_ensure(id) {
return;
}
if self.flows[id].ready && self.flows[id].goal_col == col && self.flows[id].goal_row == row
{
return;
}
let (cols, rows) = (self.grid_cols, self.grid_rows);
if col < 0 || row < 0 || col >= cols || row >= rows {
return;
}
let mut walk = alloc::vec![false; (cols * rows) as usize];
for r in 0..rows {
for c in 0..cols {
walk[(r * cols + c) as usize] = !self.is_solid(c, r);
}
}
let f = &mut self.flows[id];
f.goal_col = col;
f.goal_row = row;
f.ready = true;
for d in f.dist.iter_mut() {
*d = u16::MAX;
}
f.queue.clear();
let start = (row * cols + col) as usize;
f.dist[start] = 0;
f.queue.push(start as i32);
let mut head = 0usize;
while head < f.queue.len() {
let cur = f.queue[head] as usize;
head += 1;
let d = f.dist[cur];
if d == u16::MAX {
continue;
}
let nd = d.saturating_add(1);
let cc = (cur as i32) % cols;
let cr = (cur as i32) / cols;
if cc > 0 {
let i = cur - 1;
if walk[i] && f.dist[i] > nd {
f.dist[i] = nd;
f.queue.push(i as i32);
}
}
if cc < cols - 1 {
let i = cur + 1;
if walk[i] && f.dist[i] > nd {
f.dist[i] = nd;
f.queue.push(i as i32);
}
}
if cr > 0 {
let i = cur - cols as usize;
if walk[i] && f.dist[i] > nd {
f.dist[i] = nd;
f.queue.push(i as i32);
}
}
if cr < rows - 1 {
let i = cur + cols as usize;
if walk[i] && f.dist[i] > nd {
f.dist[i] = nd;
f.queue.push(i as i32);
}
}
}
}
fn flow_dist(&self, id: usize, col: i32, row: i32) -> i32 {
if id >= MAX_FLOWS {
return -1;
}
let f = &self.flows[id];
if !f.ready || col < 0 || row < 0 || col >= f.cols || row >= f.rows {
return -1;
}
let d = f.dist[(row * f.cols + col) as usize];
if d == u16::MAX {
-1
} else {
d as i32
}
}
fn set_seek(&mut self, e: i32, field: i32, arrive: i32, stride: i32, anim_speed: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
self.seek[s] = Seek {
field,
arrive: arrive.max(0),
stride,
anim_speed: anim_speed.max(1),
done: false,
};
self.mask[s] |= C_SEEK;
self.used |= C_SEEK;
}
}
fn clear_seek(&mut self, e: i32) {
if let Some(s) = self.slot_of(e) {
self.mask[s] &= !C_SEEK;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
}
}
fn seek_arrived(&self, e: i32) -> bool {
self.slot_of(e).map(|s| self.seek[s].done).unwrap_or(true)
}
fn seek_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_SEEK | C_TOPDOWN) {
continue;
}
if self.stun[s] > 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
if self.has(s, C_SOLDIER) && self.soldier[s].target >= 0 {
continue;
}
let sk = self.seek[s];
let id = sk.field as usize;
if id >= MAX_FLOWS || !self.flows[id].ready {
continue;
}
let (px, py) = self.seek_centre(s);
let col = px >> 4;
let row = py >> 4;
let (gc, gr) = (self.flows[id].goal_col, self.flows[id].goal_row);
let d_px = (px - (gc * 16 + 8)).abs() + (py - (gr * 16 + 8)).abs();
if d_px <= sk.arrive.max(4) {
self.seek[s].done = true;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
if sk.stride > 0 && !self.has(s, C_DIRANIM) {
let base = self.topdown[s].facing * sk.stride;
let e = encode(s as u32, self.gen[s]);
self.anim_play(e, base, 1, 1, false);
}
continue;
}
self.seek[s].done = false;
let here = self.flow_dist(id, col, row);
if here < 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
if here == 0 {
let gx = gc * 16 + 8;
let gy = gr * 16 + 8;
let td = &mut self.topdown[s];
td.dx = if (px - gx).abs() <= 2 {
0
} else if px < gx {
1
} else {
-1
};
td.dy = if (py - gy).abs() <= 2 {
0
} else if py < gy {
1
} else {
-1
};
continue;
}
let west = self.flow_dist(id, col - 1, row);
let east = self.flow_dist(id, col + 1, row);
let north = self.flow_dist(id, col, row - 1);
let south = self.flow_dist(id, col, row + 1);
let better = |n: i32| n >= 0 && n < here;
let mut mx = 0;
let mut my = 0;
if better(west) && (!better(east) || west <= east) {
mx = -1;
} else if better(east) {
mx = 1;
}
if better(north) && (!better(south) || north <= south) {
my = -1;
} else if better(south) {
my = 1;
}
if mx != 0 && my == 0 {
let cy = row * 16 + 8;
if (py - cy).abs() > 2 {
my = if py < cy { 1 } else { -1 };
}
} else if my != 0 && mx == 0 {
let cx = col * 16 + 8;
if (px - cx).abs() > 2 {
mx = if px < cx { 1 } else { -1 };
}
}
let owns_frame = self.has(s, C_DIRANIM);
let td = &mut self.topdown[s];
td.dx = mx;
td.dy = my;
if sk.stride > 0 {
if mx < 0 {
td.facing = 2;
} else if mx > 0 {
td.facing = 3;
} else if my < 0 {
td.facing = 1;
} else if my > 0 {
td.facing = 0;
}
if !owns_frame {
let base = td.facing * sk.stride;
let moving = mx != 0 || my != 0;
let e = encode(s as u32, self.gen[s]);
if moving {
self.anim_play(e, base + 1, 4, sk.anim_speed, true);
} else {
self.anim_play(e, base, 1, 1, false);
}
}
}
}
}
fn terrain_load(&mut self, cols: i32, rows: i32, gids: Option<&Value>, solid: Option<&Value>) {
self.terr_cols = cols;
self.terr_rows = rows;
self.terr.clear();
if let Some(Value::Array(a)) = gids {
let b = a.borrow();
self.terr.reserve(b.len());
for v in b.iter() {
self.terr.push(match v {
Value::Number(f) => *f as i32,
_ => 0,
});
}
}
self.terr_shown = alloc::vec![-1i32; 256];
self.terr_win = alloc::vec![-1i32; 256];
self.grid_setup(cols, rows);
if let Some(Value::Array(a)) = solid {
let b = a.borrow();
for r in 0..rows {
for c in 0..cols {
let i = (r * cols + c) as usize;
let on = match b.get(i) {
Some(Value::Number(f)) => *f != 0.0,
_ => false,
};
if on {
self.grid_set_solid(c, r, true);
}
}
}
}
}
fn terrain_set(&mut self, col: i32, row: i32, gid: i32, solid: i32) {
if col < 0 || row < 0 || col >= self.terr_cols || row >= self.terr_rows {
return;
}
let i = (row * self.terr_cols + col) as usize;
if i < self.terr.len() {
self.terr[i] = gid;
}
self.grid_set_solid(col, row, solid != 0);
}
fn terrain_blit(
&mut self,
bg: i32,
tileset: i32,
ts_cols: i32,
cam_x: i32,
cam_y: i32,
gid_unseen: i32,
) -> i32 {
if self.terr_cols <= 0 {
return 0;
}
let (cols, rows) = (self.terr_cols, self.terr_rows);
let c0 = cam_x >> 4;
let r0 = cam_y >> 4;
let mut wrote = 0;
for dr in 0..11 {
let r = r0 + dr;
if r < 0 || r >= rows {
continue;
}
for dc in 0..16 {
let c = c0 + dc;
if c < 0 || c >= cols {
continue;
}
let map_i = r * cols + c;
let mut gid = self.terr[map_i as usize];
if gid_unseen > 0
&& self.fog.on
&& self
.fog
.state
.get(map_i as usize)
.copied()
.unwrap_or(FOG_VISIBLE)
== FOG_UNSEEN
{
gid = gid_unseen;
}
let bgi = (((r & 15) * 16) + (c & 15)) as usize;
if self.terr_win[bgi] == map_i && self.terr_shown[bgi] == gid {
continue;
}
self.terr_win[bgi] = map_i;
self.terr_shown[bgi] = gid;
tish_agb::native_tilemap_set(bg, tileset, ts_cols, c & 15, r & 15, gid);
wrote += 1;
}
}
wrote
}
fn seek_centre(&self, s: usize) -> (i32, i32) {
let (mut hw, mut hh) = (0, 0);
if self.has(s, C_COLLIDER) {
hw = self.collider[s].w.floor() / 2;
hh = self.collider[s].h.floor() / 2;
}
(
self.transform[s].x.floor() + hw,
self.transform[s].y.floor() + hh,
)
}
fn set_soldier(&mut self, e: i32, team: i32, range: i32, dmg: i32, cooldown: i32) {
if let Some(s) = self.slot_of(e) {
self.soldier[s] = Soldier {
team,
range: range.max(1),
dmg: dmg.max(0),
cooldown: cooldown.max(1),
timer: 0,
target: -1,
recheck: 0,
};
self.mask[s] |= C_SOLDIER;
self.used |= C_SOLDIER;
}
}
fn soldier_team(&self, e: i32) -> i32 {
self.slot_of(e).map(|s| self.soldier[s].team).unwrap_or(-1)
}
fn soldier_target(&self, e: i32) -> i32 {
self.slot_of(e)
.map(|s| self.soldier[s].target)
.unwrap_or(-1)
}
fn soldier_system(&mut self) {
const ACQUIRE_PERIOD: i32 = 8;
let n = self.alive.len();
for s in 0..n {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_SOLDIER) {
continue;
}
if self.soldier[s].timer > 0 {
self.soldier[s].timer -= 1;
}
if self.stun[s] > 0 {
continue;
}
let (sx, sy) = self.seek_centre(s);
let team = self.soldier[s].team;
let range = self.soldier[s].range;
let aggro = range * 4;
let mut tgt = self.soldier[s].target;
if tgt >= 0 {
match self.slot_of(tgt) {
Some(ts) if self.alive[ts] && self.health_alive_slot(ts) => {
let (ox, oy) = self.seek_centre(ts);
if (ox - sx).abs() + (oy - sy).abs() > aggro + 16 {
tgt = -1;
}
}
_ => tgt = -1,
}
}
self.soldier[s].recheck -= 1;
if tgt < 0 && self.soldier[s].recheck <= 0 {
self.soldier[s].recheck = ACQUIRE_PERIOD;
let mut best = -1;
let mut best_d = aggro + 1;
for o in 0..n {
if o == s || !self.alive[o] || !self.has(o, C_SOLDIER) {
continue;
}
if self.soldier[o].team == team || !self.health_alive_slot(o) {
continue;
}
let (ox, oy) = self.seek_centre(o);
let d = (ox - sx).abs() + (oy - sy).abs();
if d <= best_d {
best_d = d;
best = encode(o as u32, self.gen[o]);
}
}
tgt = best;
} else if tgt < 0 {
self.soldier[s].recheck = self.soldier[s].recheck.max(s as i32 & 7);
}
self.soldier[s].target = tgt;
if tgt < 0 {
continue;
}
let Some(ts) = self.slot_of(tgt) else {
continue;
};
let (ox, oy) = self.seek_centre(ts);
let (dx, dy) = (ox - sx, oy - sy);
if self.has(s, C_TOPDOWN) {
self.topdown[s].facing = if dx.abs() > dy.abs() {
if dx < 0 {
2
} else {
3
}
} else if dy < 0 {
1
} else {
0
};
}
if dx.abs() + dy.abs() > range {
if self.has(s, C_TOPDOWN) {
let td = &mut self.topdown[s];
td.dx = if dx.abs() <= 2 {
0
} else if dx > 0 {
1
} else {
-1
};
td.dy = if dy.abs() <= 2 {
0
} else if dy > 0 {
1
} else {
-1
};
}
continue;
}
if self.has(s, C_TOPDOWN) {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
}
if self.soldier[s].timer == 0 {
let dmg = self.soldier[s].dmg;
self.soldier[s].timer = self.soldier[s].cooldown;
if dmg > 0 {
self.damage(tgt, dmg);
}
}
}
}
fn health_alive_slot(&self, s: usize) -> bool {
!self.has(s, C_HEALTH) || self.health[s].hp > 0
}
fn set_vision(&mut self, e: i32, radius: i32) {
if let Some(s) = self.slot_of(e) {
self.vision[s] = Vision {
radius: radius.max(0),
last_col: i32::MIN,
last_row: i32::MIN,
};
if radius > 0 {
self.mask[s] |= C_VISION;
self.used |= C_VISION;
} else {
self.mask[s] &= !C_VISION;
}
}
}
fn fog_init(&mut self, cols: i32, rows: i32) {
let cells = (cols.max(0) * rows.max(0)) as usize;
self.fog.cols = cols;
self.fog.rows = rows;
self.fog.state = alloc::vec![0u8; cells];
self.fog.shown = alloc::vec![0xFFu8; 256];
self.fog.win = alloc::vec![-1i32; 256];
self.fog.on = cells > 0;
self.fog_settled = false;
}
fn fog_reveal(&mut self, col: i32, row: i32, radius: i32) {
if !self.fog.on {
return;
}
let (cols, rows) = (self.fog.cols, self.fog.rows);
let rr = radius * radius;
for dr in -radius..=radius {
let r = row + dr;
if r < 0 || r >= rows {
continue;
}
let base = r * cols;
for dc in -radius..=radius {
let c = col + dc;
if c < 0 || c >= cols {
continue;
}
if dc * dc + dr * dr <= rr {
self.fog.state[(base + c) as usize] = FOG_VISIBLE;
}
}
}
}
fn fog_system(&mut self) {
if !self.fog.on {
return;
}
let mut moved = false;
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_VISION | C_TRANSFORM)
{
continue;
}
let (cx, cy) = self.seek_centre(s);
let (col, row) = (cx >> 4, cy >> 4);
if self.vision[s].last_col != col || self.vision[s].last_row != row {
self.vision[s].last_col = col;
self.vision[s].last_row = row;
moved = true;
}
}
if !moved && self.fog_settled {
return;
}
self.fog_settled = true;
for v in self.fog.state.iter_mut() {
if *v == FOG_VISIBLE {
*v = FOG_EXPLORED;
}
}
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_VISION | C_TRANSFORM)
{
continue;
}
let (cx, cy) = self.seek_centre(s);
let col = cx >> 4;
let row = cy >> 4;
let r = self.vision[s].radius;
self.fog_reveal(col, row, r);
}
}
#[allow(clippy::too_many_arguments)]
fn fog_blit(
&mut self,
bg: i32,
tileset: i32,
ts_cols: i32,
cam_x: i32,
cam_y: i32,
gid_unseen: i32,
gid_explored: i32,
) -> i32 {
if !self.fog.on {
return 0;
}
if self.fog.shown.len() < 256 {
self.fog.shown = alloc::vec![0xFFu8; 256];
self.fog.win = alloc::vec![-1i32; 256];
}
let (cols, rows) = (self.fog.cols, self.fog.rows);
let c0 = cam_x >> 4;
let r0 = cam_y >> 4;
let mut wrote = 0;
for dr in 0..11 {
let r = r0 + dr;
if r < 0 || r >= rows {
continue;
}
for dc in 0..16 {
let c = c0 + dc;
if c < 0 || c >= cols {
continue;
}
let map_i = r * cols + c;
let st = self.fog.state[map_i as usize];
let bgi = (((r & 15) * 16) + (c & 15)) as usize;
if self.fog.win[bgi] == map_i && self.fog.shown[bgi] == st {
continue;
}
self.fog.win[bgi] = map_i;
self.fog.shown[bgi] = st;
let gid = match st {
FOG_VISIBLE => 0,
FOG_EXPLORED => gid_explored,
_ => gid_unseen,
};
tish_agb::native_tilemap_set(bg, tileset, ts_cols, c & 15, r & 15, gid);
wrote += 1;
}
}
wrote
}
fn snap_topdown_to_tile(&mut self, s: usize) {
if !self.has(s, C_TRANSFORM | C_COLLIDER) {
return;
}
let w = self.collider[s].w;
let h = self.collider[s].h;
let cx = self.transform[s].x.floor() + w.floor() / 2;
let cy = self.transform[s].y.floor() + h.floor() / 2;
let col = cx.div_euclid(TILE);
let row = cy.div_euclid(TILE);
self.transform[s].x = Fixed::from_raw(col * TILE * 256) + Fixed::from_raw(8 * 256) - w / 2;
self.transform[s].y = Fixed::from_raw(row * TILE * 256) + Fixed::from_raw(8 * 256) - h / 2;
}
fn hopper_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(_ts) = self.slot_of(target) else {
return;
};
let lp = self.lure_point();
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_HOPPER | C_TOPDOWN) || !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
let same_room = match self.camera_target.and_then(|e| self.slot_of(e)) {
Some(ts) => self.same_room(s, ts),
None => true,
};
if !same_room {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
let h = &mut self.hopper[s];
let px = self.transform[s].x;
let py = self.transform[s].y;
if h.state == 0 {
h.timer -= 1;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
if h.timer <= 0 {
h.state = 1;
h.timer = 32; h.start_x = px;
h.start_y = py;
if let Some((lx, ly)) = self.lured_to(s, lp) {
let ddx = lx - px.floor();
let ddy = ly - py.floor();
if ddx.abs() > ddy.abs() {
self.hopper[s].dir_x = ddx.signum();
self.hopper[s].dir_y = 0;
} else {
self.hopper[s].dir_x = 0;
self.hopper[s].dir_y = ddy.signum();
}
continue;
}
self.rng = self.rng.wrapping_mul(1664525).wrapping_add(1013904223);
let r = (self.rng >> 16) % 4;
if r == 0 {
self.hopper[s].dir_x = 1;
self.hopper[s].dir_y = 0;
} else if r == 1 {
self.hopper[s].dir_x = -1;
self.hopper[s].dir_y = 0;
} else if r == 2 {
self.hopper[s].dir_x = 0;
self.hopper[s].dir_y = 1;
} else {
self.hopper[s].dir_x = 0;
self.hopper[s].dir_y = -1;
}
}
} else {
let dist = (px - h.start_x).abs() + (py - h.start_y).abs();
if dist >= Fixed::from_raw(16 * 256) || h.timer <= 0 {
h.state = 0;
h.timer = 30 + (px.floor() % 30).abs();
h.dir_x = 0;
h.dir_y = 0;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
self.topdown[s].snap_dx = 0;
self.topdown[s].snap_dy = 0;
if self.topdown[s].snap_mode == TD_SNAP_TILE {
self.snap_topdown_to_tile(s);
} else {
let snap_x = (px.floor() + 4) & !7;
let snap_y = (py.floor() + 4) & !7;
self.transform[s].x = Fixed::from_raw(snap_x * 256);
self.transform[s].y = Fixed::from_raw(snap_y * 256);
}
} else {
h.timer -= 1;
}
}
if self.hopper[s].state == 1 {
self.topdown[s].dx = self.hopper[s].dir_x;
self.topdown[s].dy = self.hopper[s].dir_y;
}
let td = &mut self.topdown[s];
let c_stride = self.hopper[s].stride;
if c_stride > 0 {
let mx = td.dx;
let my = td.dy;
if mx < 0 {
td.facing = 2;
} else if mx > 0 {
td.facing = 3;
} else if my < 0 {
td.facing = 1;
} else if my > 0 {
td.facing = 0;
}
let base = td.facing * c_stride;
let moving = mx != 0 || my != 0;
let e = encode(s as u32, self.gen[s]);
if moving {
self.anim_play(e, base + 1, 4, 8, true);
} else {
self.anim_play(e, base, 1, 1, false);
}
} else {
let e = encode(s as u32, self.gen[s]);
self.anim_play(e, 0, 2, 6, true);
}
}
}
fn jumper_system(&mut self) {
let Some(target) = self.camera_target else {
return;
};
let Some(_ts) = self.slot_of(target) else {
return;
};
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_JUMPER | C_TOPDOWN) || !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
let same_room = match self.camera_target.and_then(|e| self.slot_of(e)) {
Some(ts) => self.same_room(s, ts),
None => true,
};
if !same_room {
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
continue;
}
let px = self.transform[s].x;
let _py = self.transform[s].y;
if self.jumper[s].state == 0 {
self.jumper[s].timer -= 1;
self.topdown[s].dx = 0;
self.topdown[s].dy = 0;
if self.jumper[s].timer <= 0 {
self.jumper[s].state = 1; self.jumper[s].z = Fixed::from_raw(0);
self.jumper[s].dz = Fixed::from_raw(400);
self.rng = self.rng.wrapping_mul(1664525).wrapping_add(1013904223);
let r = (self.rng >> 16) % 8;
if r == 0 {
self.jumper[s].dx = Fixed::from_raw(2 * 256);
self.jumper[s].dy = Fixed::from_raw(2 * 256);
} else if r == 1 {
self.jumper[s].dx = Fixed::from_raw(-2 * 256);
self.jumper[s].dy = Fixed::from_raw(2 * 256);
} else if r == 2 {
self.jumper[s].dx = Fixed::from_raw(-2 * 256);
self.jumper[s].dy = Fixed::from_raw(-2 * 256);
} else if r == 3 {
self.jumper[s].dx = Fixed::from_raw(2 * 256);
self.jumper[s].dy = Fixed::from_raw(-2 * 256);
} else if r == 4 {
self.jumper[s].dx = Fixed::from_raw(2 * 256);
self.jumper[s].dy = Fixed::from_raw(0);
} else if r == 5 {
self.jumper[s].dx = Fixed::from_raw(-2 * 256);
self.jumper[s].dy = Fixed::from_raw(0);
} else if r == 6 {
self.jumper[s].dx = Fixed::from_raw(0);
self.jumper[s].dy = Fixed::from_raw(2 * 256);
} else {
self.jumper[s].dx = Fixed::from_raw(0);
self.jumper[s].dy = Fixed::from_raw(-2 * 256);
}
}
} else {
let dx = self.jumper[s].dx;
let dy = self.jumper[s].dy;
self.transform[s].x += dx;
self.transform[s].y += dy;
let dz = self.jumper[s].dz;
self.jumper[s].z += dz;
self.jumper[s].dz -= Fixed::from_raw(40);
if self.jumper[s].z <= Fixed::from_raw(0) {
self.jumper[s].z = Fixed::from_raw(0);
self.jumper[s].state = 0;
self.jumper[s].timer = 30 + (px.floor() % 60).abs();
if self.topdown[s].snap_mode == TD_SNAP_TILE {
self.snap_topdown_to_tile(s);
} else {
let snap_x = (self.transform[s].x.floor() + 4) & !7;
let snap_y = (self.transform[s].y.floor() + 4) & !7;
self.transform[s].x = Fixed::from_raw(snap_x * 256);
self.transform[s].y = Fixed::from_raw(snap_y * 256);
}
}
if self.has(s, C_SPRITE) {
self.sprite[s].oy = -self.jumper[s].z.floor();
}
}
let e = encode(s as u32, self.gen[s]);
if self.jumper[s].state == 1 {
self.anim_play(e, 1, 1, 1, false);
} else {
self.anim_play(e, 0, 1, 1, false);
}
}
}
fn topdown_speed(&mut self, e: i32, px: f64) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
self.topdown[s].speed = (px * 256.0) as i32;
}
}
fn topdown_move(&mut self, e: i32, dx: i32, dy: i32) {
if self.input_locked(e) {
return;
}
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
let (dx, dy) = (dx.signum(), dy.signum());
self.topdown[s].dx = dx;
self.topdown[s].dy = dy;
if dx < 0 {
self.topdown[s].facing = 2;
} else if dx > 0 {
self.topdown[s].facing = 3;
} else if dy < 0 {
self.topdown[s].facing = 1;
} else if dy > 0 {
self.topdown[s].facing = 0;
}
}
}
fn topdown_knockback(&mut self, e: i32, dx: i32, dy: i32, power: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
let p = if power > 0 { power } else { TD_KNOCK };
self.topdown[s].kx = Fixed::from_raw(dx.signum() * p);
self.topdown[s].ky = Fixed::from_raw(dy.signum() * p);
self.topdown[s].knock = TD_KNOCK_FRAMES;
}
}
fn topdown_system(&mut self) {
let zero = Fixed::from_raw(0);
let frozen = if self.room_cam.enabled && self.room_cam.transitioning {
self.camera_target.and_then(|e| self.slot_of(e))
} else {
None
};
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_TOPDOWN | C_TRANSFORM | C_COLLIDER) {
continue;
}
if Some(s) == frozen || !self.is_active(s) {
continue;
}
let mut td = self.topdown[s];
if td.snap_mode == TD_SNAP_TILE
&& td.snap_dx == 0
&& td.snap_dy == 0
&& (td.dx != 0 || td.dy != 0)
{
if td.dx != 0 {
td.dy = 0;
}
td.snap_dx = td.dx;
td.snap_dy = td.dy;
let base_x =
self.transform[s].x - Fixed::from_raw(8 * 256) + self.collider[s].w / 2;
let base_y =
self.transform[s].y - Fixed::from_raw(8 * 256) + self.collider[s].h / 2;
let col = if td.dx > 0 {
base_x.floor().div_euclid(TILE) + 1
} else if td.dx < 0 {
(base_x.to_raw() + TILE * 256 - 1).div_euclid(TILE * 256) - 1
} else {
(base_x + Fixed::from_raw(8 * 256)).floor().div_euclid(TILE)
};
let row = if td.dy > 0 {
base_y.floor().div_euclid(TILE) + 1
} else if td.dy < 0 {
(base_y.to_raw() + TILE * 256 - 1).div_euclid(TILE * 256) - 1
} else {
(base_y + Fixed::from_raw(8 * 256)).floor().div_euclid(TILE)
};
td.snap_target_x = Fixed::from_raw(col * TILE * 256) + Fixed::from_raw(8 * 256)
- self.collider[s].w / 2;
td.snap_target_y = Fixed::from_raw(row * TILE * 256) + Fixed::from_raw(8 * 256)
- self.collider[s].h / 2;
self.topdown[s] = td;
}
let (w, h) = (self.collider[s].w, self.collider[s].h);
let had_intent = td.dx != 0 || td.dy != 0;
let (mut vx, mut vy) = if td.knock > 0 {
(td.kx, td.ky)
} else {
let sp = if td.speed == 0 { TD_WALK } else { td.speed };
let (use_dx, use_dy) =
if td.snap_mode == TD_SNAP_TILE && (td.snap_dx != 0 || td.snap_dy != 0) {
(td.snap_dx, td.snap_dy)
} else {
(td.dx, td.dy)
};
let diag = use_dx != 0 && use_dy != 0;
let axis = |d: i32| {
if d == 0 {
zero
} else {
let raw = if diag { (sp * TD_DIAG) / 256 } else { sp };
Fixed::from_raw(d.signum() * raw)
}
};
(axis(use_dx), axis(use_dy))
};
let (x, y) = (self.transform[s].x, self.transform[s].y);
let confine = self.room_cam.enabled && self.has(s, C_CHASE);
let (rmin_x, rmax_x, rmin_y, rmax_y) = if confine {
let tw = self.room_cam.room_w * TILE;
let th = self.room_cam.room_h * TILE;
let cx = x.floor() + w.floor() / 2;
let cy = y.floor() + h.floor() / 2;
let room_x = cx.div_euclid(tw);
let room_y = cy.div_euclid(th);
(
Fixed::from_raw(room_x * tw * 256),
Fixed::from_raw((room_x + 1) * tw * 256) - w,
Fixed::from_raw(room_y * th * 256),
Fixed::from_raw((room_y + 1) * th * 256) - h,
)
} else {
(zero, zero, zero, zero)
};
let embedded = self.box_hits_solid(x, y, w, h);
let nx = x + vx;
let mut rx = if embedded {
nx
} else if vx > zero && self.box_hits_solid(nx, y, w, h) {
let col = (((nx + w).to_raw() - 1) >> 8).div_euclid(TILE);
vx = zero;
Fixed::from_raw(col * TILE * 256) - w
} else if vx < zero && self.box_hits_solid(nx, y, w, h) {
let col = nx.floor().div_euclid(TILE);
vx = zero;
Fixed::from_raw((col + 1) * TILE * 256)
} else {
nx
};
if vx != zero {
if let Some((bx, _, bw, _)) = self.first_blocker_hit(s, rx, y, w, h) {
rx = if vx > zero { bx - w } else { bx + bw };
vx = zero;
}
}
let ny = y + vy;
let mut ry = if embedded {
ny
} else if vy > zero && self.box_hits_solid(rx, ny, w, h) {
let row = (((ny + h).to_raw() - 1) >> 8).div_euclid(TILE);
vy = zero;
Fixed::from_raw(row * TILE * 256) - h
} else if vy < zero && self.box_hits_solid(rx, ny, w, h) {
let row = ny.floor().div_euclid(TILE);
vy = zero;
Fixed::from_raw((row + 1) * TILE * 256)
} else {
ny
};
if vy != zero {
if let Some((_, by, _, bh)) = self.first_blocker_hit(s, rx, ry, w, h) {
ry = if vy > zero { by - h } else { by + bh };
vy = zero;
}
}
if confine {
if rx < rmin_x {
rx = rmin_x;
} else if rx > rmax_x {
rx = rmax_x;
}
if ry < rmin_y {
ry = rmin_y;
} else if ry > rmax_y {
ry = rmax_y;
}
}
if td.snap_mode == TD_SNAP_TILE && (td.snap_dx != 0 || td.snap_dy != 0) {
let over_x = if td.snap_dx > 0 {
rx >= td.snap_target_x
} else {
rx <= td.snap_target_x
};
let over_y = if td.snap_dy > 0 {
ry >= td.snap_target_y
} else {
ry <= td.snap_target_y
};
let hit_wall = (td.snap_dx != 0 && vx == zero) || (td.snap_dy != 0 && vy == zero);
if (td.snap_dx != 0 && over_x) || (td.snap_dy != 0 && over_y) || hit_wall {
let match_intent = if td.snap_dx != 0 {
td.dx == td.snap_dx
} else {
td.dy == td.snap_dy
};
if !hit_wall && match_intent {
rx = td.snap_target_x;
ry = td.snap_target_y;
self.topdown[s].snap_target_x =
td.snap_target_x + Fixed::from_raw(td.snap_dx * TILE * 256);
self.topdown[s].snap_target_y =
td.snap_target_y + Fixed::from_raw(td.snap_dy * TILE * 256);
} else {
if hit_wall {
self.transform[s].x = rx;
self.transform[s].y = ry;
self.snap_topdown_to_tile(s);
rx = self.transform[s].x;
ry = self.transform[s].y;
} else {
rx = td.snap_target_x;
ry = td.snap_target_y;
}
self.topdown[s].snap_dx = 0;
self.topdown[s].snap_dy = 0;
}
}
}
self.transform[s].x = rx;
self.transform[s].y = ry;
let td = &mut self.topdown[s];
td.moving = td.knock <= 0 && had_intent; if td.knock > 0 {
td.knock -= 1;
if td.knock == 0 {
td.snap_dx = 0;
td.snap_dy = 0;
}
}
let knock_left = td.knock;
let snap_busy = td.snap_dx != 0 || td.snap_dy != 0;
let mode = td.snap_mode;
td.dx = 0;
td.dy = 0;
if mode == TD_SNAP_TILE && knock_left == 0 && !snap_busy {
self.snap_topdown_to_tile(s);
}
}
}
fn swing(
&mut self,
attacker: i32,
target_tag: i32,
damage: i32,
reach: i32,
size: i32,
ttl: i32,
) -> i32 {
let Some(s) = self.slot_of(attacker) else {
return 0;
};
let facing = if self.has(s, C_TOPDOWN) {
self.topdown[s].facing
} else if self.has(s, C_PLATFORMER) {
if self.platformer[s].face < 0 {
2
} else {
3
}
} else {
0
};
let ax = self.transform[s].x.floor();
let ay = self.transform[s].y.floor();
let cw = self.collider[s].w.floor();
let ch = self.collider[s].h.floor();
let (cx, cy) = (ax + cw / 2, ay + ch / 2);
let (bx, by) = match facing {
1 => (cx - size / 2, ay - reach - size), 2 => (ax - reach - size, cy - size / 2), 3 => (ax + cw + reach, cy - size / 2), _ => (cx - size / 2, ay + ch + reach), };
let e = self.spawn();
if let Some(ss) = self.slot_of(e) {
self.transform[ss] = Transform {
x: to_fixed(bx as f64),
y: to_fixed(by as f64),
};
self.collider[ss] = Collider {
w: to_fixed(size as f64),
h: to_fixed(size as f64),
};
self.hurt[ss] = Hurt {
damage,
target_tag,
despawn_on_hit: false,
stun: 0,
damage_type: 0,
};
self.life[ss] = Life {
ttl: ttl.max(1),
offscreen: false,
};
self.mask[ss] |= C_TRANSFORM | C_COLLIDER | C_HURT | C_LIFE;
self.used |= C_TRANSFORM | C_COLLIDER | C_HURT | C_LIFE;
}
e
}
fn set_health(&mut self, e: i32, max: i32, invuln_max: i32) {
if let Some(s) = self.slot_of(e) {
let m = max.max(1);
self.health[s] = Health {
hp: m,
max: m,
invuln: 0,
invuln_max: invuln_max.max(0),
dead: false,
};
self.mask[s] |= C_HEALTH;
self.used |= C_HEALTH;
}
}
fn damage(&mut self, e: i32, amount: i32) -> bool {
let mut e = e;
for _ in 0..4 {
let Some(s) = self.slot_of(e) else { break };
let m2 = self.mask2[s];
if m2 & (M2_PHASED | M2_HIDDEN) != 0 {
return false;
}
if m2 & M2_GATE != 0 && self.zx[s].gate == 0 {
return false;
}
if m2 & M2_PROXY != 0 {
let p = self.zx[s].proxy;
if p >= 0 && p != e && self.slot_of(p).is_some() {
e = p;
continue;
}
}
break;
}
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_HEALTH)) {
if self.health[s].invuln > 0 || self.health[s].dead || amount <= 0 {
return false;
}
let h = &mut self.health[s];
h.hp -= amount;
h.invuln = h.invuln_max;
if h.hp <= 0 {
h.hp = 0;
h.dead = true;
}
return true;
}
false
}
fn heal(&mut self, e: i32, amount: i32) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_HEALTH)) {
let h = &mut self.health[s];
if !h.dead {
h.hp = (h.hp + amount.max(0)).min(h.max);
}
}
}
fn set_lifetime(&mut self, e: i32, ttl: i32) {
if let Some(s) = self.slot_of(e) {
let offscreen = self.has(s, C_LIFE) && self.life[s].offscreen;
self.life[s] = Life {
ttl: ttl.max(0),
offscreen,
};
self.mask[s] |= C_LIFE;
self.used |= C_LIFE;
}
}
fn set_despawn_offscreen(&mut self, e: i32, on: bool) {
if let Some(s) = self.slot_of(e) {
let ttl = if self.has(s, C_LIFE) {
self.life[s].ttl
} else {
0
};
self.life[s] = Life { ttl, offscreen: on };
self.mask[s] |= C_LIFE;
self.used |= C_LIFE;
}
}
fn set_dir_anim(&mut self, e: i32, base: i32, stride: i32, frames: i32, speed: i32) {
if let Some(s) = self.slot_of(e) {
self.diranim[s] = DirAnim {
base,
stride: stride.max(0),
frames: frames.max(1),
speed: speed.max(1),
};
self.mask[s] |= C_DIRANIM;
self.used |= C_DIRANIM;
}
}
fn diranim_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_DIRANIM | C_TOPDOWN)
{
continue;
}
let d = self.diranim[s];
let facing = self.topdown[s].facing.clamp(0, 3);
let e = encode(s as u32, self.gen[s]);
self.anim_play(e, d.base + facing * d.stride, d.frames, d.speed, true);
}
}
fn set_guard(&mut self, e: i32, mask: i32) {
if let Some(s) = self.slot_of(e) {
self.guard[s] = mask;
if mask == 0 {
self.mask[s] &= !C_GUARD;
} else {
self.mask[s] |= C_GUARD;
self.used |= C_GUARD;
}
}
}
fn guard_blocks(&self, t: usize, hcx: i32, hcy: i32, is_shot: bool) -> bool {
let want = if is_shot { GUARD_SHOT } else { GUARD_MELEE };
if self.guard[t] & want == 0 || !self.has(t, C_TOPDOWN) {
return false;
}
let vcx = self.transform[t].x.floor() + self.collider[t].w.floor() / 2;
let vcy = self.transform[t].y.floor() + self.collider[t].h.floor() / 2;
let (dx, dy) = (hcx - vcx, hcy - vcy);
match self.topdown[t].facing {
1 => dy < 0 && dy.abs() >= dx.abs(),
2 => dx < 0 && dx.abs() >= dy.abs(),
3 => dx > 0 && dx.abs() >= dy.abs(),
_ => dy > 0 && dy.abs() >= dx.abs(),
}
}
fn set_hurt(&mut self, e: i32, damage: i32, target_tag: i32, despawn_on_hit: bool, stun: i32) {
if let Some(s) = self.slot_of(e) {
self.hurt[s] = Hurt {
damage,
target_tag,
despawn_on_hit,
stun,
damage_type: 0,
};
self.mask[s] |= C_HURT;
self.used |= C_HURT;
}
}
fn set_bullet_style(
&mut self,
sheet: i32,
frame: i32,
size: i32,
damage: i32,
target: i32,
tag: i32,
ttl: i32,
) {
let dt = self.bullet_style.damage_type;
self.bullet_style = BulletStyle {
sheet,
frame,
size,
damage,
target,
tag,
ttl,
damage_type: dt,
};
}
fn fire_bullet(&mut self, cx: Fixed, cy: Fixed, vx: Fixed, vy: Fixed) -> i32 {
let st = self.bullet_style;
let size_f = Fixed::from_raw(st.size * 256);
let half = size_f / 2;
let sp = tish_agb::sprite_new_typed(st.sheet);
tish_agb::sprite_set_frame_typed(sp, st.frame);
let off = (st.size - 16) / 2;
let e = self.spawn();
if let Some(s) = self.slot_of(e) {
self.transform[s] = Transform {
x: cx - half,
y: cy - half,
};
self.collider[s] = Collider {
w: size_f,
h: size_f,
};
self.body[s] = Body { vx, vy };
self.sprite[s] = SpriteRef {
handle: sp,
ox: off,
oy: off,
};
self.tag[s] = st.tag;
self.mask[s] |= C_TRANSFORM | C_COLLIDER | C_BODY | C_SPRITE;
self.used |= C_TRANSFORM | C_COLLIDER | C_BODY | C_SPRITE;
}
self.set_hurt(e, st.damage, st.target, true, 0);
if st.damage_type != 0 {
if let Some(s) = self.slot_of(e) {
self.hurt[s].damage_type = st.damage_type;
}
}
self.set_lifetime(e, st.ttl);
self.set_despawn_offscreen(e, true);
e
}
fn fire_angle(&mut self, cx: Fixed, cy: Fixed, deg: Fixed, speed: Fixed) -> i32 {
let rev = deg / Fixed::from_raw(360 * 256);
self.fire_bullet(cx, cy, speed * rev.cos(), speed * rev.sin())
}
fn fire_ring(&mut self, cx: Fixed, cy: Fixed, count: i32, speed: Fixed) {
if count <= 0 {
return;
}
let count_f = Fixed::from_raw(count * 256);
for k in 0..count {
let rev = Fixed::from_raw(k * 256) / count_f;
self.fire_bullet(cx, cy, speed * rev.cos(), speed * rev.sin());
}
}
fn fire_spread(
&mut self,
cx: Fixed,
cy: Fixed,
center_deg: Fixed,
count: i32,
spread_deg: Fixed,
speed: Fixed,
) {
if count <= 0 {
return;
}
if count == 1 {
self.fire_angle(cx, cy, center_deg, speed);
return;
}
let start = center_deg - spread_deg / 2;
let step = spread_deg / Fixed::from_raw((count - 1) * 256);
for k in 0..count {
let deg = start + step * Fixed::from_raw(k * 256);
self.fire_angle(cx, cy, deg, speed);
}
}
fn fire_aimed(&mut self, cx: Fixed, cy: Fixed, tox: Fixed, toy: Fixed, speed: Fixed) -> i32 {
let dx = tox - cx;
let dy = toy - cy;
let len = (dx * dx + dy * dy).sqrt();
if len.to_raw() == 0 {
return self.fire_bullet(cx, cy, Fixed::from_raw(0), speed);
}
self.fire_bullet(cx, cy, speed * dx / len, speed * dy / len)
}
fn set_topdown_snap(&mut self, e: i32, mode: u8) {
if let Some(s) = self.slot_of(e).filter(|&s| self.has(s, C_TOPDOWN)) {
self.topdown[s].snap_mode = mode;
}
}
fn set_mover(&mut self, e: i32, pattern: u8, base_vy: Fixed, amp: Fixed, period: i32) {
if let Some(s) = self.slot_of(e) {
self.mover[s] = Mover {
pattern,
t: 0,
base_vy,
amp,
period,
};
self.body[s] = Body {
vx: Fixed::from_raw(0),
vy: base_vy,
};
self.mask[s] |= C_MOVER | C_BODY;
self.used |= C_MOVER | C_BODY;
}
}
fn mover_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_MOVER | C_BODY) || !self.is_active(s) {
continue;
}
let m = self.mover[s];
let t = m.t + 1;
self.mover[s].t = t;
let vx = if m.pattern == 1 {
tri_fixed(t, m.period, m.amp)
} else {
Fixed::from_raw(0)
};
self.body[s].vx = vx;
self.body[s].vy = m.base_vy;
}
}
fn life_system(&mut self) {
let mut dead: Vec<i32> = Vec::new();
let mut pooled: Vec<i32> = Vec::new();
for s in 0..self.alive.len() {
if !self.alive[s] || self.mask[s] & C_SLEEP != 0 || !self.has(s, C_LIFE) {
continue;
}
let mut remove = false;
if self.life[s].ttl > 0 {
self.life[s].ttl -= 1;
if self.life[s].ttl == 0 {
remove = true;
}
}
if !remove && self.life[s].offscreen && self.has(s, C_TRANSFORM) && !self.on_screen(s) {
remove = true;
}
if !remove
&& self.grid_cols > 0
&& self.has(s, C_BODY | C_HURT | C_TRANSFORM | C_COLLIDER)
{
let (t, c) = (self.transform[s], self.collider[s]);
if self.box_hits_solid(t.x, t.y, c.w, c.h) {
remove = true;
}
}
if !remove
&& self.room_cam.enabled
&& self.has(s, C_BODY | C_HURT | C_TRANSFORM)
&& !self.in_current_room(s)
{
remove = true;
}
if remove {
if self.pool_of[s] >= 0 {
pooled.push(self.pool_of[s]);
} else {
dead.push(encode(s as u32, self.gen[s]));
}
}
}
for p in pooled {
self.pool_retire_packed(p);
}
for e in dead {
self.despawn(e);
}
}
fn combat_system(&mut self) {
let n = self.alive.len();
let mut hits: Vec<(i32, i32, i32, i32)> = Vec::new(); let mut consumed: Vec<i32> = Vec::new();
let mut victims: Vec<usize> = Vec::new();
for t in 0..n {
if self.alive[t] && self.is_active(t) && self.has(t, C_HEALTH | C_TRANSFORM | C_COLLIDER)
&& self.mask2[t] & M2_HIDDEN == 0
{
victims.push(t);
}
}
for h in 0..n {
if !self.alive[h]
|| !self.has(h, C_HURT | C_TRANSFORM | C_COLLIDER)
|| !self.is_active(h)
{
continue;
}
if self.mask2[h] & M2_HIDDEN != 0 {
continue;
}
let hurt = self.hurt[h];
for &t in &victims {
if t == h {
continue;
}
if self.tag[t] != hurt.target_tag || !self.slots_overlap(h, t) {
continue;
}
if !self.same_room(h, t) {
continue;
}
let hcx = self.transform[h].x.floor() + self.collider[h].w.floor() / 2;
let hcy = self.transform[h].y.floor() + self.collider[h].h.floor() / 2;
if self.has(t, C_GUARD) && self.guard_blocks(t, hcx, hcy, hurt.despawn_on_hit) {
if hurt.despawn_on_hit {
consumed.push(encode(h as u32, self.gen[h]));
}
break;
}
if hurt.damage_type != 0 && (self.immune[t] & hurt.damage_type) != 0 {
if hurt.despawn_on_hit {
consumed.push(encode(h as u32, self.gen[h]));
}
break;
}
if self.weak[t] != 0
&& (hurt.damage_type == 0 || (self.weak[t] & hurt.damage_type) == 0)
{
if hurt.despawn_on_hit {
consumed.push(encode(h as u32, self.gen[h]));
}
break;
}
hits.push((encode(t as u32, self.gen[t]), hurt.damage, hcx, hcy));
if hurt.stun > 0 {
self.stun[t] = self.stun[t].max(hurt.stun);
}
if hurt.despawn_on_hit {
consumed.push(encode(h as u32, self.gen[h]));
}
break; }
}
for (victim, dmg, hcx, hcy) in hits {
if self.damage(victim, dmg) {
if let Some(t) = self.slot_of(victim).filter(|&t| self.has(t, C_TOPDOWN)) {
let vcx = self.transform[t].x.floor() + self.collider[t].w.floor() / 2;
let vcy = self.transform[t].y.floor() + self.collider[t].h.floor() / 2;
self.topdown[t].kx = Fixed::from_raw((vcx - hcx).signum() * TD_KNOCK);
self.topdown[t].ky = Fixed::from_raw((vcy - hcy).signum() * TD_KNOCK);
self.topdown[t].knock = TD_KNOCK_FRAMES;
}
}
}
for bullet in consumed {
self.despawn(bullet);
}
}
fn health_system(&mut self) {
for s in 0..self.alive.len() {
if self.mask[s] & C_SLEEP != 0 {
continue;
}
if self.alive[s] && self.stun[s] > 0 {
self.stun[s] -= 1;
}
if !self.alive[s] || !self.has(s, C_HEALTH) {
continue;
}
let inv = self.health[s].invuln;
if inv > 0 {
self.health[s].invuln = inv - 1;
}
if self.has(s, C_SPRITE) {
let handle = self.sprite[s].handle;
if handle >= 0 {
let vis = (inv <= 0 || (inv / 4) % 2 == 0) && self.mask2[s] & M2_HIDDEN == 0;
tish_agb::native_sprite_set_visible(handle, vis);
}
}
}
}
fn collect_deaths(&mut self) -> Vec<(Value, Value, i32)> {
let mut out = Vec::new();
for s in 0..self.alive.len() {
if !self.alive[s]
|| self.mask[s] & C_SLEEP != 0
|| !self.has(s, C_HEALTH)
|| !self.health[s].dead
{
continue;
}
self.health[s].dead = false;
if self.mask2[s] & M2_NOTE != 0 && self.death_notes.len() < 16 {
self.death_notes.push(self.zx[s].code);
}
let entity = encode(s as u32, self.gen[s]);
let (cb, data) = match &self.behaviour[s] {
Some(b) => (self.defs[b.def].on_death.clone(), b.data.clone()),
None => (Value::Null, Value::Null),
};
out.push((cb, data, entity));
}
out
}
fn set_patrol(&mut self, e: i32, flip_mode: i32) {
if let Some(s) = self.slot_of(e) {
self.patrol[s] = Patrol {
flip_mode,
..Patrol::default()
};
self.mask[s] |= C_PATROL;
self.used |= C_PATROL;
}
}
fn patrol_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_PATROL | C_PLATFORMER | C_TRANSFORM | C_COLLIDER) {
continue;
}
if !self.is_active(s) {
continue;
}
if self.stun[s] > 0 {
self.platformer[s].dir = 0;
continue;
}
let dir = self.patrol[s].dir;
if !self.platformer[s].grounded {
self.platformer[s].dir = dir; continue;
}
let x = self.transform[s].x.floor();
let y = self.transform[s].y.floor();
let w = self.collider[s].w.floor();
let h = self.collider[s].h.floor();
let probe_x = if dir > 0 { x + w } else { x - 1 };
let ahead_col = probe_x.div_euclid(TILE);
let foot_row = (y + h).div_euclid(TILE);
let ledge = !self.is_solid(ahead_col, foot_row);
let dir = if self.platformer[s].blocked || ledge {
-dir
} else {
dir
};
self.patrol[s].dir = dir;
self.platformer[s].dir = dir;
let mode = self.patrol[s].flip_mode;
if mode != 0 && self.patrol[s].flipped_for != dir && self.has(s, C_SPRITE) {
self.patrol[s].flipped_for = dir;
let handle = self.sprite[s].handle;
if handle >= 0 {
let flip = if mode == 1 { dir > 0 } else { dir < 0 };
tish_agb::native_sprite_set_flip(handle, flip);
}
}
}
}
fn set_anim(&mut self, e: i32, frames: i32, speed: i32) {
self.anim_play(e, 0, frames, speed, true);
}
fn anim_play(&mut self, e: i32, from: i32, len: i32, speed: i32, looping: bool) {
let Some(s) = self.slot_of(e) else {
return;
};
let len = len.max(1);
let same = {
let a = &self.anim[s];
self.has(s, C_ANIM) && a.playing && a.from == from && a.len == len
};
let a = &mut self.anim[s];
a.from = from;
a.len = len;
a.speed = speed.max(1);
a.looping = looping;
a.playing = true;
self.mask[s] |= C_ANIM;
self.used |= C_ANIM;
if !same {
a.timer = 0;
a.cur = 0;
let handle = self.sprite[s].handle;
if handle >= 0 {
tish_agb::native_sprite_set_frame(handle, from);
}
}
}
fn set_walk(&mut self, e: i32, cols: i32, speed: i32) {
if let Some(s) = self.slot_of(e) {
self.walk[s] = Walk {
cols: cols.max(1),
speed: speed.max(1),
timer: 0,
phase: false,
};
self.mask[s] |= C_WALK;
self.used |= C_WALK;
}
}
fn grid_col(&self, e: i32) -> i32 {
self.slot_of(e)
.filter(|&s| self.has(s, C_GRIDPOS))
.map(|s| self.gridpos[s].col)
.unwrap_or(-1)
}
fn grid_row(&self, e: i32) -> i32 {
self.slot_of(e)
.filter(|&s| self.has(s, C_GRIDPOS))
.map(|s| self.gridpos[s].row)
.unwrap_or(-1)
}
fn grid_facing(&self, e: i32) -> i32 {
if let Some(s) = self.slot_of(e) {
if self.has(s, C_GRIDPOS) {
let g = &self.gridpos[s];
return if g.fy > 0 {
0
} else if g.fy < 0 {
1
} else if g.fx < 0 {
2
} else {
3
};
}
}
0
}
fn anim_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s]
|| self.mask[s] & C_SLEEP != 0
|| !self.has(s, C_ANIM | C_SPRITE)
|| !self.anim[s].playing
{
continue;
}
if !self.is_active(s) {
continue; }
let a = &mut self.anim[s];
a.timer += 1;
if a.timer >= a.speed {
a.timer = 0;
a.cur += 1;
if a.cur >= a.len {
if a.looping {
a.cur = 0;
} else {
a.cur = a.len - 1;
a.playing = false;
}
}
let frame = a.from + a.cur;
let handle = self.sprite[s].handle;
if handle >= 0 {
tish_agb::native_sprite_set_frame(handle, frame);
}
}
}
}
fn walk_system(&mut self) {
for s in 0..self.alive.len() {
if !self.alive[s] || !self.has(s, C_WALK | C_GRIDPOS | C_SPRITE) {
continue;
}
if !self.is_active(s) {
continue;
}
let (fx, fy, moving) = {
let g = &self.gridpos[s];
(g.fx, g.fy, g.moving)
};
let (row, flip) = if fy > 0 {
(0, false) } else if fy < 0 {
(1, false) } else if fx < 0 {
(2, false) } else if fx > 0 {
(2, true) } else {
(0, false) };
let cols = self.walk[s].cols.max(1);
let col = if moving {
let w = &mut self.walk[s];
w.timer += 1;
if w.timer >= w.speed {
w.timer = 0;
w.phase = !w.phase;
}
if w.phase {
2
} else {
0
}
} else {
let w = &mut self.walk[s];
w.timer = 0;
w.phase = false;
1 };
let frame = row * cols + col.min(cols - 1);
let handle = self.sprite[s].handle;
if handle >= 0 {
tish_agb::native_sprite_set_frame(handle, frame);
tish_agb::native_sprite_set_flip(handle, flip);
}
}
}
fn render_system(&mut self) {
for s in 0..self.alive.len() {
if self.alive[s] && self.mask[s] & C_SLEEP == 0 && self.has(s, C_TRANSFORM | C_SPRITE) {
let h = self.sprite[s].handle;
if h < 0 {
continue;
}
if self.is_active(s) {
tish_agb::native_sprite_restore(h);
tish_agb::native_sprite_set_pos(
h,
self.transform[s].x.floor() + self.sprite[s].ox,
self.transform[s].y.floor() + self.sprite[s].oy,
);
} else {
tish_agb::native_sprite_release(h);
}
}
}
}
fn set_camera_target(&mut self, e: i32) {
self.camera_target = Some(e);
self.room_cam.enabled = false;
}
fn set_room_camera(&mut self, e: i32, room_w: i32, room_h: i32) {
self.camera_target = Some(e);
let rw = if room_w > 0 { room_w } else { 15 };
let rh = if room_h > 0 { room_h } else { 10 };
let (rx, ry) = match self.slot_of(e) {
Some(s) if self.has(s, C_GRIDPOS) => {
(self.gridpos[s].col / rw, self.gridpos[s].row / rh)
}
Some(s) if self.has(s, C_TRANSFORM) => {
let (cx, cy) = self.camera_focus(s);
(cx.div_euclid(rw * TILE), cy.div_euclid(rh * TILE))
}
_ => (0, 0),
};
self.room_cam.enabled = true;
self.room_cam.room_w = rw;
self.room_cam.room_h = rh;
self.room_cam.cur_rx = rx;
self.room_cam.cur_ry = ry;
self.room_cam.transitioning = false;
}
fn update_camera(&mut self) {
if self.room_cam.enabled {
let rc = &self.room_cam;
let (cx, cy) = if rc.transitioning {
(
rc.from_cam.0 + (rc.to_cam.0 - rc.from_cam.0) * rc.timer / rc.dur,
rc.from_cam.1 + (rc.to_cam.1 - rc.from_cam.1) * rc.timer / rc.dur,
)
} else {
(rc.cur_rx * rc.room_w * TILE, rc.cur_ry * rc.room_h * TILE)
};
self.cam_x = cx;
self.cam_y = cy;
tish_agb::native_camera_set(cx, cy);
return;
}
let Some(e) = self.camera_target else { return };
let Some(s) = self.slot_of(e) else { return };
if !self.has(s, C_TRANSFORM) {
return;
}
let (px, py) = (self.transform[s].x.floor(), self.transform[s].y.floor());
let max_x = (self.grid_cols * TILE - 240).max(0);
let max_y = (self.grid_rows * TILE - 160).max(0);
let cx = (px + TILE / 2 - 120).clamp(0, max_x);
let cy = (py + TILE / 2 - 80).clamp(0, max_y);
self.cam_x = cx;
self.cam_y = cy;
tish_agb::native_camera_set(cx, cy);
}
fn on_screen(&self, s: usize) -> bool {
if !self.has(s, C_TRANSFORM) {
return true;
}
let x = self.transform[s].x.floor();
let y = self.transform[s].y.floor();
let (w, h) = if self.has(s, C_COLLIDER) {
(
self.collider[s].w.floor().max(1),
self.collider[s].h.floor().max(1),
)
} else {
(TILE, TILE)
};
let m = CULL_MARGIN;
x + w >= self.cam_x - m
&& x <= self.cam_x + 240 + m
&& y + h >= self.cam_y - m
&& y <= self.cam_y + 160 + m
}
fn is_active(&self, s: usize) -> bool {
if self.mask[s] & C_SLEEP != 0 {
return false;
}
match self.camera_target {
None => true,
Some(e) if self.slot_of(e) == Some(s) => true,
_ => self.on_screen(s),
}
}
fn camera_focus(&self, s: usize) -> (i32, i32) {
let (bx, by) = if self.has(s, C_COLLIDER) {
(
self.collider[s].w.floor() / 2,
self.collider[s].h.floor() / 2,
)
} else {
(0, 0)
};
(
self.transform[s].x.floor() + bx,
self.transform[s].y.floor() + by,
)
}
fn room_of(&self, s: usize) -> (i32, i32) {
let (rw, rh) = (self.room_cam.room_w, self.room_cam.room_h);
let (cx, cy) = self.camera_focus(s);
(cx.div_euclid(rw * TILE), cy.div_euclid(rh * TILE))
}
fn same_room(&self, a: usize, b: usize) -> bool {
if !self.room_cam.enabled {
return true;
}
if self.room_cam.transitioning {
return false;
}
self.room_of(a) == self.room_of(b)
}
fn in_current_room(&self, s: usize) -> bool {
if !self.room_cam.enabled {
return true;
}
if self.room_cam.transitioning {
return false;
}
let (rx, ry) = self.room_of(s);
rx == self.room_cam.cur_rx && ry == self.room_cam.cur_ry
}
fn nearest_tag(&self, e: i32, tag: i32, radius: i32) -> i32 {
let Some(s) = self.slot_of(e) else {
return -1;
};
let (cx, cy) = self.center_of(s);
let mut best = -1;
let mut best_d = i32::MAX;
for c in 0..self.alive.len() {
if c == s || !self.alive[c] || self.tag[c] != tag || !self.has(c, C_TRANSFORM) {
continue;
}
if !self.same_room(s, c) {
continue;
}
let (ox, oy) = self.center_of(c);
let d = (ox - cx).to_raw().abs() + (oy - cy).to_raw().abs();
if d < best_d {
best_d = d;
best = encode(c as u32, self.gen[c]);
}
}
if best_d <= radius.saturating_mul(256) {
best
} else {
-1
}
}
fn entity_dist(&self, a: i32, b: i32) -> i32 {
match (self.slot_of(a), self.slot_of(b)) {
(Some(sa), Some(sb)) => {
let (ax, ay) = self.center_of(sa);
let (bx, by) = self.center_of(sb);
((ax - bx).to_raw().abs() + (ay - by).to_raw().abs()) >> 8
}
_ => -1,
}
}
fn room_track_free(&mut self) {
if !self.room_cam.enabled || self.room_cam.transitioning {
return;
}
let Some(e) = self.camera_target else { return };
let Some(s) = self.slot_of(e) else { return };
if self.has(s, C_GRIDPOS) || !self.has(s, C_TRANSFORM) {
return;
}
let (rw, rh) = (self.room_cam.room_w, self.room_cam.room_h);
let (cx, cy) = self.camera_focus(s);
let (nrx, nry) = (cx.div_euclid(rw * TILE), cy.div_euclid(rh * TILE));
if (nrx, nry) != (self.room_cam.cur_rx, self.room_cam.cur_ry) {
self.begin_room_transition_free(s, nrx, nry);
}
}
fn begin_room_transition_free(&mut self, s: usize, nrx: i32, nry: i32) {
let rc = &self.room_cam;
let (rw, rh) = (rc.room_w, rc.room_h);
let from_cam = (rc.cur_rx * rw * TILE, rc.cur_ry * rh * TILE);
let to_cam = (nrx * rw * TILE, nry * rh * TILE);
let px = (self.transform[s].x.floor(), self.transform[s].y.floor());
let dx = nrx - rc.cur_rx;
let dy = nry - rc.cur_ry;
let rc = &mut self.room_cam;
rc.cur_rx = nrx;
rc.cur_ry = nry;
rc.transitioning = true;
rc.timer = 0;
rc.from_cam = from_cam;
rc.to_cam = to_cam;
rc.from_px = px;
rc.to_px = (px.0 + dx * 24, px.1 + dy * 24);
}
fn is_solid(&self, col: i32, row: i32) -> bool {
if col < 0 || row < 0 || col >= self.grid_cols || row >= self.grid_rows {
return true;
}
let i = (row * self.grid_cols + col) as usize;
grid_bit(&self.solid, i)
}
fn is_oneway(&self, col: i32, row: i32) -> bool {
if col < 0 || row < 0 || col >= self.grid_cols || row >= self.grid_rows {
return false;
}
if self.oneway.is_empty() {
return false;
}
let i = (row * self.grid_cols + col) as usize;
grid_bit(&self.oneway, i)
}
fn is_ladder(&self, col: i32, row: i32) -> bool {
if col < 0 || row < 0 || col >= self.grid_cols || row >= self.grid_rows {
return false;
}
if self.ladder.is_empty() {
return false;
}
let i = (row * self.grid_cols + col) as usize;
grid_bit(&self.ladder, i)
}
fn grid_setup(&mut self, cols: i32, rows: i32) {
self.grid_cols = cols.max(0);
self.grid_rows = rows.max(0);
let n = (self.grid_cols * self.grid_rows) as usize;
self.grid_cells = n;
let bytes = grid_bit_bytes(n);
if bytes > self.solid.capacity() {
self.solid = alloc::vec![0u8; bytes];
} else {
self.solid.resize(bytes, 0);
self.solid.fill(0);
}
if !self.oneway.is_empty() {
if bytes > self.oneway.capacity() {
self.oneway = alloc::vec![0u8; bytes];
} else {
self.oneway.resize(bytes, 0);
self.oneway.fill(0);
}
}
if !self.ladder.is_empty() {
if bytes > self.ladder.capacity() {
self.ladder = alloc::vec![0u8; bytes];
} else {
self.ladder.resize(bytes, 0);
self.ladder.fill(0);
}
}
}
fn grid_set_solid(&mut self, col: i32, row: i32, solid: bool) {
if col >= 0 && row >= 0 && col < self.grid_cols && row < self.grid_rows {
let i = (row * self.grid_cols + col) as usize;
grid_bit_set(&mut self.solid, i, solid);
}
}
fn grid_set_oneway(&mut self, col: i32, row: i32, on: bool) {
if col >= 0 && row >= 0 && col < self.grid_cols && row < self.grid_rows {
let bytes = grid_bit_bytes(self.grid_cells);
if self.oneway.len() < bytes {
self.oneway.resize(bytes, 0);
}
let i = (row * self.grid_cols + col) as usize;
grid_bit_set(&mut self.oneway, i, on);
}
}
fn grid_set_ladder(&mut self, col: i32, row: i32, on: bool) {
if col >= 0 && row >= 0 && col < self.grid_cols && row < self.grid_rows {
let bytes = grid_bit_bytes(self.grid_cells);
if self.ladder.len() < bytes {
self.ladder.resize(bytes, 0);
}
let i = (row * self.grid_cols + col) as usize;
grid_bit_set(&mut self.ladder, i, on);
}
}
fn attach_grid(&mut self, e: i32, col: i32, row: i32) {
if let Some(s) = self.slot_of(e) {
let px = Fixed::from_raw(col * TILE * 256);
let py = Fixed::from_raw(row * TILE * 256);
self.gridpos[s] = GridPos {
col,
row,
moving: false,
tx: px,
ty: py,
fx: 0,
fy: 1,
};
self.transform[s] = Transform { x: px, y: py };
self.mask[s] |= C_GRIDPOS | C_TRANSFORM;
self.used |= C_GRIDPOS | C_TRANSFORM;
}
}
fn grid_step(&mut self, e: i32, dx: i32, dy: i32) {
if self.room_cam.enabled && self.room_cam.transitioning && self.camera_target == Some(e) {
return;
}
let Some(s) = self.slot_of(e) else {
return;
};
if !self.has(s, C_GRIDPOS) {
return;
}
let (sdx, sdy) = if dx != 0 {
(dx.signum(), 0)
} else if dy != 0 {
(0, dy.signum())
} else {
return;
};
let (col, row, moving) = {
let g = &mut self.gridpos[s];
g.fx = sdx;
g.fy = sdy;
(g.col, g.row, g.moving)
};
if moving {
return;
}
let (tc, tr) = (col + sdx, row + sdy);
if self.is_solid(tc, tr) || self.tile_occupied(tc, tr, s) {
return;
}
if self.room_cam.enabled && self.camera_target == Some(e) {
let (rw, rh) = (self.room_cam.room_w, self.room_cam.room_h);
let (old_rx, old_ry) = (col / rw, row / rh);
let (new_rx, new_ry) = (tc / rw, tr / rh);
if (new_rx, new_ry) != (old_rx, old_ry) {
self.begin_room_transition(s, tc, tr, new_rx, new_ry);
return;
}
}
let g = &mut self.gridpos[s];
g.col = tc;
g.row = tr;
g.tx = Fixed::from_raw(tc * TILE * 256);
g.ty = Fixed::from_raw(tr * TILE * 256);
g.moving = true;
}
fn begin_room_transition(&mut self, s: usize, tc: i32, tr: i32, new_rx: i32, new_ry: i32) {
let rc = &self.room_cam;
let (rw, rh) = (rc.room_w, rc.room_h);
let from_cam = (rc.cur_rx * rw * TILE, rc.cur_ry * rh * TILE);
let to_cam = (new_rx * rw * TILE, new_ry * rh * TILE);
let from_px = (self.gridpos[s].tx.floor(), self.gridpos[s].ty.floor());
let to_px = (tc * TILE, tr * TILE);
let g = &mut self.gridpos[s];
g.col = tc;
g.row = tr;
g.tx = Fixed::from_raw(tc * TILE * 256);
g.ty = Fixed::from_raw(tr * TILE * 256);
g.moving = false;
let rc = &mut self.room_cam;
rc.cur_rx = new_rx;
rc.cur_ry = new_ry;
rc.transitioning = true;
rc.timer = 0;
rc.from_cam = from_cam;
rc.to_cam = to_cam;
rc.from_px = from_px;
rc.to_px = to_px;
}
fn room_transition_system(&mut self) {
if !self.room_cam.enabled || !self.room_cam.transitioning {
return;
}
let Some(e) = self.camera_target else { return };
let Some(s) = self.slot_of(e) else { return };
let rc = &mut self.room_cam;
rc.timer += 1;
let done = rc.timer >= rc.dur;
let (px, py) = if done {
rc.transitioning = false;
rc.to_px
} else {
(
rc.from_px.0 + (rc.to_px.0 - rc.from_px.0) * rc.timer / rc.dur,
rc.from_px.1 + (rc.to_px.1 - rc.from_px.1) * rc.timer / rc.dur,
)
};
self.transform[s].x = Fixed::from_raw(px * 256);
self.transform[s].y = Fixed::from_raw(py * 256);
}
fn tile_occupied(&self, col: i32, row: i32, except: usize) -> bool {
for o in 0..self.alive.len() {
if o != except
&& self.alive[o]
&& self.has(o, C_GRIDPOS)
&& self.gridpos[o].col == col
&& self.gridpos[o].row == row
{
return true;
}
}
false
}
fn grid_system(&mut self) {
let speed = Fixed::from_raw(GRID_SPEED * 256);
for s in 0..self.alive.len() {
if !self.alive[s]
|| self.mask[s] & C_SLEEP != 0
|| !self.has(s, C_GRIDPOS)
|| !self.gridpos[s].moving
{
continue;
}
let (tx, ty) = (self.gridpos[s].tx, self.gridpos[s].ty);
let ax = approach(&mut self.transform[s].x, tx, speed);
let ay = approach(&mut self.transform[s].y, ty, speed);
if ax && ay {
self.gridpos[s].moving = false;
}
}
}
fn collect_interact(&self, e: i32) -> Option<(Value, Value, i32, i32)> {
let s = self.slot_of(e)?;
if !self.has(s, C_GRIDPOS) {
return None;
}
let g = self.gridpos[s];
let (tc, tr) = (g.col + g.fx, g.row + g.fy);
for o in 0..self.alive.len() {
if o == s || !self.alive[o] || !self.has(o, C_GRIDPOS) {
continue;
}
if self.gridpos[o].col == tc && self.gridpos[o].row == tr {
if let Some(b) = &self.behaviour[o] {
let cb = self.defs[b.def].on_interact.clone();
if !matches!(cb, Value::Null) {
let target = encode(o as u32, self.gen[o]);
return Some((cb, b.data.clone(), target, e));
}
}
}
}
None
}
fn collect_topdown_interact(&self, e: i32, reach: i32) -> Option<(Value, Value, i32, i32)> {
let s = self.slot_of(e)?;
if !self.has(s, C_TOPDOWN | C_TRANSFORM | C_COLLIDER) {
return None;
}
let ax = self.transform[s].x.floor();
let ay = self.transform[s].y.floor();
let cw = self.collider[s].w.floor();
let ch = self.collider[s].h.floor();
let r = reach.max(1);
let (px, py, pw, ph) = match self.topdown[s].facing {
1 => (ax, ay - r, cw, r),
2 => (ax - r, ay, r, ch),
3 => (ax + cw, ay, r, ch),
_ => (ax, ay + ch, cw, r),
};
for o in 0..self.alive.len() {
if o == s
|| !self.alive[o]
|| !self.has(o, C_TRANSFORM | C_COLLIDER)
|| self.behaviour[o].is_none()
{
continue;
}
if !self.same_room(s, o) {
continue;
}
let ox = self.transform[o].x.floor();
let oy = self.transform[o].y.floor();
let ow = self.collider[o].w.floor();
let oh = self.collider[o].h.floor();
if px < ox + ow && ox < px + pw && py < oy + oh && oy < py + ph {
let b = self.behaviour[o].as_ref().unwrap();
let cb = self.defs[b.def].on_interact.clone();
if !matches!(cb, Value::Null) {
return Some((cb, b.data.clone(), encode(o as u32, self.gen[o]), e));
}
}
}
None
}
fn collect_platformer_interact(&self, e: i32, reach: i32) -> Option<(Value, Value, i32, i32)> {
let s = self.slot_of(e)?;
if !self.has(s, C_PLATFORMER | C_TRANSFORM | C_COLLIDER) {
return None;
}
let ax = self.transform[s].x.floor();
let ay = self.transform[s].y.floor();
let cw = self.collider[s].w.floor();
let ch = self.collider[s].h.floor();
let r = reach.max(1);
let px = if self.platformer[s].face < 0 {
ax - r
} else {
ax
};
let pw = cw + r;
let py = ay - PF_INTERACT_PAD;
let ph = ch + 2 * PF_INTERACT_PAD;
for o in 0..self.alive.len() {
if o == s
|| !self.alive[o]
|| !self.has(o, C_TRANSFORM | C_COLLIDER)
|| self.behaviour[o].is_none()
{
continue;
}
if !self.same_room(s, o) {
continue;
}
let ox = self.transform[o].x.floor();
let oy = self.transform[o].y.floor();
let ow = self.collider[o].w.floor();
let oh = self.collider[o].h.floor();
if px < ox + ow && ox < px + pw && py < oy + oh && oy < py + ph {
let b = self.behaviour[o].as_ref().unwrap();
let cb = self.defs[b.def].on_interact.clone();
if !matches!(cb, Value::Null) {
return Some((cb, b.data.clone(), encode(o as u32, self.gen[o]), e));
}
}
}
None
}
fn slots_overlap(&self, a: usize, b: usize) -> bool {
let (ta, ca) = (self.transform[a], self.collider[a]);
let (tb, cb) = (self.transform[b], self.collider[b]);
ta.x < tb.x + cb.w && tb.x < ta.x + ca.w && ta.y < tb.y + cb.h && tb.y < ta.y + ca.h
}
fn entities_overlap(&self, ea: i32, eb: i32) -> bool {
match (self.slot_of(ea), self.slot_of(eb)) {
(Some(a), Some(b))
if self.has(a, C_TRANSFORM | C_COLLIDER)
&& self.has(b, C_TRANSFORM | C_COLLIDER) =>
{
self.slots_overlap(a, b)
}
_ => false,
}
}
fn collect_collisions(&self) -> Vec<(Value, Value, i32, i32)> {
let mut out = Vec::new();
let n = self.alive.len();
for a in 0..n {
if !self.alive[a] || !self.has(a, C_TRANSFORM | C_COLLIDER) || !self.is_active(a) {
continue;
}
let responds = match &self.behaviour[a] {
Some(b) => !matches!(self.defs[b.def].on_collide, Value::Null),
None => false,
};
if !responds {
continue;
}
let ea = encode(a as u32, self.gen[a]);
for b in 0..n {
if b == a
|| !self.alive[b]
|| !self.has(b, C_TRANSFORM | C_COLLIDER)
|| !self.is_active(b)
{
continue;
}
if !self.slots_overlap(a, b) {
continue;
}
if !self.same_room(a, b) {
continue;
}
let eb = encode(b as u32, self.gen[b]);
self.push_on_collide(&mut out, a, ea, eb);
}
}
out
}
fn push_on_collide(
&self,
out: &mut Vec<(Value, Value, i32, i32)>,
slot: usize,
me_entity: i32,
other_entity: i32,
) {
if let Some(b) = &self.behaviour[slot] {
let cb = self.defs[b.def].on_collide.clone();
if !matches!(cb, Value::Null) {
out.push((cb, b.data.clone(), me_entity, other_entity));
}
}
}
}
static WORLD: SingleCore<RefCell<World>> = SingleCore::new(RefCell::new(World::new()));
fn with_world<R>(f: impl FnOnce(&mut World) -> R) -> R {
WORLD.with(|c| f(&mut c.borrow_mut()))
}
#[allow(dead_code)] fn read_i32_arr(v: Option<&Value>) -> Vec<i32> {
match v {
Some(Value::Array(a)) => a
.borrow()
.iter()
.map(|x| match x {
Value::Number(f) => *f as i32,
_ => 0,
})
.collect(),
_ => Vec::new(),
}
}
fn n(args: &[Value], i: usize) -> f64 {
match args.get(i) {
Some(Value::Number(x)) => *x,
_ => 0.0,
}
}
fn name_of(args: &[Value], i: usize) -> String {
args.get(i)
.map(|v| v.to_display_string())
.unwrap_or_default()
}
pub fn spawn(_args: &[Value]) -> Value {
Value::Number(with_world(|w| w.spawn()) as f64)
}
pub fn despawn(args: &[Value]) -> Value {
with_world(|w| w.despawn(n(args, 0) as i32));
Value::Null
}
pub fn reset_entity(args: &[Value]) -> Value {
with_world(|w| w.reset_entity(n(args, 0) as i32));
Value::Null
}
pub fn set_stun(args: &[Value]) -> Value {
set_stun_typed(n(args, 0) as i32, n(args, 1) as i32);
Value::Null
}
pub fn is_stunned(args: &[Value]) -> Value {
Value::Number(is_stunned_typed(n(args, 0) as i32) as f64)
}
pub fn set_dynamic(args: &[Value]) -> Value {
set_dynamic_typed(
n(args, 0) as i32,
to_fixed(n(args, 1)),
n(args, 2) as i32,
n(args, 3) as i32,
to_fixed(n(args, 4)),
n(args, 5) as i32,
);
Value::Null
}
pub fn body_impulse(args: &[Value]) -> Value {
body_impulse_typed(n(args, 0) as i32, n(args, 1) as i32, to_fixed(n(args, 2)));
Value::Null
}
pub fn body_kick(args: &[Value]) -> Value {
body_kick_typed(
n(args, 0) as i32,
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
to_fixed(n(args, 3)),
);
Value::Null
}
pub fn body_asleep(args: &[Value]) -> Value {
Value::Number(body_asleep_typed(n(args, 0) as i32) as f64)
}
pub fn body_speed2(args: &[Value]) -> Value {
Value::Number(body_speed2_typed(n(args, 0) as i32) as f64)
}
pub fn body_last_hit(args: &[Value]) -> Value {
Value::Number(body_last_hit_typed(n(args, 0) as i32) as f64)
}
pub fn grid_set_surface(args: &[Value]) -> Value {
grid_set_surface_typed(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32);
Value::Null
}
pub fn surface_def(args: &[Value]) -> Value {
surface_def_typed(
n(args, 0) as i32,
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
n(args, 3) as i32,
);
Value::Null
}
pub fn pool_new(args: &[Value]) -> Value {
Value::Number(pool_new_typed(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
) as f64)
}
pub fn pool_arm(args: &[Value]) -> Value {
Value::Number(pool_arm_typed(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
) as f64)
}
pub fn pool_retire(args: &[Value]) -> Value {
pool_retire_typed(n(args, 0) as i32, n(args, 1) as i32);
Value::Null
}
pub fn pool_clear(args: &[Value]) -> Value {
pool_clear_typed(n(args, 0) as i32);
Value::Null
}
pub fn pool_get(args: &[Value]) -> Value {
Value::Number(pool_get_typed(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32) as f64)
}
pub fn pool_stat(args: &[Value]) -> Value {
Value::Number(pool_stat_typed(n(args, 0) as i32, n(args, 1) as i32) as f64)
}
pub fn clear_world(_args: &[Value]) -> Value {
with_world(|w| w.clear_world());
Value::Null
}
pub fn set_transform(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let x = to_fixed(n(args, 1));
let y = to_fixed(n(args, 2));
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.transform[s] = Transform { x, y };
w.mask[s] |= C_TRANSFORM;
w.used |= C_TRANSFORM;
}
});
Value::Null
}
pub fn set_body(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let vx = to_fixed(n(args, 1));
let vy = to_fixed(n(args, 2));
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.body[s] = Body { vx, vy };
w.mask[s] |= C_BODY;
w.used |= C_BODY;
}
});
Value::Null
}
pub fn set_collider(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let w = to_fixed(n(args, 1));
let h = to_fixed(n(args, 2));
with_world(|w2| {
if let Some(s) = w2.slot_of(e) {
w2.collider[s] = Collider { w, h };
w2.mask[s] |= C_COLLIDER;
w2.used |= C_COLLIDER;
}
});
Value::Null
}
pub fn overlaps(args: &[Value]) -> Value {
let a = n(args, 0) as i32;
let b = n(args, 1) as i32;
Value::Bool(with_world(|w| w.entities_overlap(a, b)))
}
pub fn set_platformer(args: &[Value]) -> Value {
with_world(|w| w.set_platformer(n(args, 0) as i32));
Value::Null
}
pub fn platformer_walk(args: &[Value]) -> Value {
with_world(|w| w.platformer_walk(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn platformer_run(args: &[Value]) -> Value {
with_world(|w| w.platformer_run(n(args, 0) as i32, truthy(args, 1)));
Value::Null
}
pub fn platformer_jump(args: &[Value]) -> Value {
with_world(|w| w.platformer_jump(n(args, 0) as i32));
Value::Null
}
pub fn platformer_jump_release(args: &[Value]) -> Value {
with_world(|w| w.platformer_jump_release(n(args, 0) as i32));
Value::Null
}
pub fn platformer_drop(args: &[Value]) -> Value {
with_world(|w| w.platformer_drop(n(args, 0) as i32));
Value::Null
}
pub fn platformer_grounded(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e)
.map(|s| w.has(s, C_PLATFORMER) && w.platformer[s].grounded)
.unwrap_or(false)
}))
}
pub fn platformer_face(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_PLATFORMER))
.map(|s| w.platformer[s].face as f64)
.unwrap_or(1.0)
}))
}
pub fn platformer_blocked(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e)
.map(|s| w.has(s, C_PLATFORMER) && w.platformer[s].blocked)
.unwrap_or(false)
}))
}
pub fn platformer_bounce(args: &[Value]) -> Value {
with_world(|w| w.platformer_bounce(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn platformer_set_vy(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let vy_raw = (n(args, 1) * 256.0) as i32;
with_world(|w| w.platformer_set_vy(e, vy_raw));
Value::Null
}
pub fn platformer_set_speed(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let walk_raw = (n(args, 1) * 256.0) as i32;
let run_raw = (n(args, 2) * 256.0) as i32;
with_world(|w| w.platformer_set_speed(e, walk_raw, run_raw));
Value::Null
}
pub fn platformer_set_physics(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let jump_raw = (n(args, 1) * 256.0) as i32;
let grav_raw = (n(args, 2) * 256.0) as i32;
with_world(|w| w.platformer_set_physics(e, jump_raw, grav_raw));
Value::Null
}
pub fn platformer_launch(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let vx_raw = (n(args, 1) * 256.0) as i32;
let vy_raw = (n(args, 2) * 256.0) as i32;
with_world(|w| w.platformer_launch(e, vx_raw, vy_raw));
Value::Null
}
pub fn platformer_vy(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_PLATFORMER))
.map(|s| w.platformer[s].vy.to_raw() as f64 / 256.0)
.unwrap_or(0.0)
}))
}
pub fn platformer_hold(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let on = n(args, 1) != 0.0;
with_world(|w| {
if let Some(s) = w.slot_of(e).filter(|&s| w.has(s, C_PLATFORMER)) {
w.platformer[s].held = on;
}
});
Value::Null
}
pub fn set_patrol(args: &[Value]) -> Value {
with_world(|w| w.set_patrol(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn tile_solid(args: &[Value]) -> Value {
Value::Bool(with_world(|w| {
w.is_solid(n(args, 0) as i32, n(args, 1) as i32)
}))
}
pub fn set_topdown(args: &[Value]) -> Value {
with_world(|w| w.set_topdown(n(args, 0) as i32));
Value::Null
}
pub fn set_blocker(args: &[Value]) -> Value {
with_world(|w| w.set_blocker(n(args, 0) as i32));
Value::Null
}
pub fn topdown_snap(args: &[Value]) -> Value {
with_world(|w| w.set_topdown_snap(n(args, 0) as i32, n(args, 1) as u8));
Value::Null
}
pub fn set_chase(args: &[Value]) -> Value {
with_world(|w| {
w.set_chase(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
)
});
Value::Null
}
pub fn flow_goal(args: &[Value]) -> Value {
with_world(|w| w.flow_goal(n(args, 0) as usize, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn flow_dist(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.flow_dist(n(args, 0) as usize, n(args, 1) as i32, n(args, 2) as i32)
}) as f64)
}
pub fn flow_ready(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
Value::Bool(with_world(|w| id < MAX_FLOWS && w.flows[id].ready))
}
pub fn set_seek(args: &[Value]) -> Value {
with_world(|w| {
w.set_seek(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
)
});
Value::Null
}
pub fn clear_seek(args: &[Value]) -> Value {
with_world(|w| w.clear_seek(n(args, 0) as i32));
Value::Null
}
pub fn seek_arrived(args: &[Value]) -> Value {
Value::Bool(with_world(|w| w.seek_arrived(n(args, 0) as i32)))
}
pub fn set_soldier(args: &[Value]) -> Value {
with_world(|w| {
w.set_soldier(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
)
});
Value::Null
}
pub fn soldier_target(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.soldier_target(n(args, 0) as i32)) as f64)
}
pub fn soldier_team(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.soldier_team(n(args, 0) as i32)) as f64)
}
pub fn terrain_load(args: &[Value]) -> Value {
let cols = n(args, 0) as i32;
let rows = n(args, 1) as i32;
with_world(|w| w.terrain_load(cols, rows, args.get(2), args.get(3)));
Value::Null
}
pub fn terrain_set(args: &[Value]) -> Value {
with_world(|w| {
w.terrain_set(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
)
});
Value::Null
}
pub fn terrain_blit(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.terrain_blit(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
n(args, 5) as i32,
)
}) as f64)
}
pub fn set_sleeping(args: &[Value]) -> Value {
with_world(|w| {
if let Some(s) = w.slot_of(n(args, 0) as i32) {
if n(args, 1) != 0.0 {
w.mask[s] |= C_SLEEP;
w.used |= C_SLEEP;
} else {
w.mask[s] &= !C_SLEEP;
}
}
});
Value::Null
}
pub fn fog_init(args: &[Value]) -> Value {
with_world(|w| w.fog_init(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_vision(args: &[Value]) -> Value {
with_world(|w| w.set_vision(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn fog_reveal(args: &[Value]) -> Value {
with_world(|w| w.fog_reveal(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn fog_state(args: &[Value]) -> Value {
let (c, r) = (n(args, 0) as i32, n(args, 1) as i32);
Value::Number(with_world(|w| {
if !w.fog.on || c < 0 || r < 0 || c >= w.fog.cols || r >= w.fog.rows {
FOG_UNSEEN as i32
} else {
w.fog.state[(r * w.fog.cols + c) as usize] as i32
}
}) as f64)
}
pub fn fog_blit(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.fog_blit(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
n(args, 5) as i32,
n(args, 6) as i32,
)
}) as f64)
}
pub fn set_wanderer(args: &[Value]) -> Value {
if let [Value::Number(e), Value::Number(turn_rate)] = args {
let (e, tr) = (*e as i32, *turn_rate as i32);
with_world(|w| {
if let Some(s) = w.slot_of(e).filter(|&s| w.has(s, C_TOPDOWN)) {
w.wanderer[s] = Wanderer {
turn_rate: tr.clamp(0, 255),
turn_timer: 0,
want_shoot: 0,
last_x: i32::MIN,
last_y: i32::MIN,
home_rx: 0,
home_ry: 0,
};
if w.room_cam.enabled {
let (rw, rh) = (w.room_cam.room_w * TILE, w.room_cam.room_h * TILE);
let (fx, fy) = w.camera_focus(s);
w.wanderer[s].home_rx = fx.div_euclid(rw);
w.wanderer[s].home_ry = fy.div_euclid(rh);
}
w.mask2[s] |= M2_WANDERER;
w.used2 |= M2_WANDERER;
}
});
}
Value::Null
}
pub fn topdown_speed_raw(args: &[Value]) -> Value {
if let [Value::Number(e), Value::Number(raw)] = args {
let (e, raw) = (*e as i32, *raw as i32);
with_world(|w| {
if let Some(s) = w.slot_of(e).filter(|&s| w.has(s, C_TOPDOWN)) {
w.topdown[s].speed = raw.max(0);
}
});
}
Value::Null
}
pub fn wanderer_wants_shot(args: &[Value]) -> Value {
if let [Value::Number(e)] = args {
let e = *e as i32;
return with_world(|w| match w.slot_of(e) {
Some(s) if w.mask2[s] & M2_WANDERER != 0 && w.wanderer_wants_shot(s) => {
Value::Number(1.0)
}
_ => Value::Number(0.0),
});
}
Value::Number(0.0)
}
pub fn set_hopper(args: &[Value]) -> Value {
if let [Value::Number(e), Value::Number(stride)] = args {
let e = *e as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.hopper[s] = Hopper {
stride: *stride as i32,
timer: 30,
state: 0,
start_x: Fixed::from_raw(0),
start_y: Fixed::from_raw(0),
dir_x: 0,
dir_y: 0,
};
w.mask[s] |= C_HOPPER;
w.used |= C_HOPPER;
}
});
}
Value::Null
}
pub fn set_jumper(args: &[Value]) -> Value {
if let [Value::Number(e)] = args {
let e = *e as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.jumper[s] = Jumper::default();
w.mask[s] |= C_JUMPER;
w.used |= C_JUMPER;
}
});
}
Value::Null
}
pub fn topdown_speed(args: &[Value]) -> Value {
with_world(|w| w.topdown_speed(n(args, 0) as i32, n(args, 1)));
Value::Null
}
pub fn topdown_move(args: &[Value]) -> Value {
with_world(|w| w.topdown_move(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn topdown_facing(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_TOPDOWN))
.map(|s| w.topdown[s].facing)
.unwrap_or(0)
}) as f64)
}
pub fn topdown_moving(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_TOPDOWN))
.map(|s| w.topdown[s].moving)
.unwrap_or(false)
}))
}
pub fn topdown_knockback(args: &[Value]) -> Value {
with_world(|w| {
w.topdown_knockback(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
)
});
Value::Null
}
pub fn swing(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let target = n(args, 1) as i32;
let dmg = n(args, 2) as i32;
let reach = n(args, 3) as i32;
let size = n(args, 4) as i32;
let ttl = n(args, 5) as i32;
Value::Number(with_world(|w| w.swing(e, target, dmg, reach, size, ttl)) as f64)
}
pub fn set_health(args: &[Value]) -> Value {
let invuln = match args.get(2) {
Some(Value::Number(v)) => *v as i32,
_ => INVULN_FRAMES,
};
with_world(|w| w.set_health(n(args, 0) as i32, n(args, 1) as i32, invuln));
Value::Null
}
pub fn set_lifetime(args: &[Value]) -> Value {
with_world(|w| w.set_lifetime(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_arena_wrap(args: &[Value]) -> Value {
with_world(|w| w.set_arena_wrap(truthy(args, 0)));
Value::Null
}
pub fn set_despawn_offscreen(args: &[Value]) -> Value {
with_world(|w| w.set_despawn_offscreen(n(args, 0) as i32, truthy(args, 1)));
Value::Null
}
pub fn set_shooter(args: &[Value]) -> Value {
with_world(|w| {
w.set_shooter(
n(args, 0) as i32,
n(args, 1) as i32,
to_fixed(n(args, 2)),
n(args, 3) as i32 != 0,
)
});
Value::Null
}
pub fn set_guard(args: &[Value]) -> Value {
with_world(|w| w.set_guard(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_dir_anim(args: &[Value]) -> Value {
with_world(|w| {
w.set_dir_anim(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
)
});
Value::Null
}
pub fn set_charger(args: &[Value]) -> Value {
with_world(|w| w.set_charger(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn entity_alive(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| match w.slot_of(e) {
Some(s) if w.alive[s] => 1.0,
_ => 0.0,
}))
}
pub fn entity_hp(args: &[Value]) -> Value {
Value::Number(entity_hp_typed(n(args, 0) as i32) as f64)
}
pub fn entity_hp_max(args: &[Value]) -> Value {
Value::Number(entity_hp_max_typed(n(args, 0) as i32) as f64)
}
pub fn set_lure(args: &[Value]) -> Value {
with_world(|w| w.set_lure(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn set_hurt(args: &[Value]) -> Value {
with_world(|w| {
w.set_hurt(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
truthy(args, 3),
n(args, 4) as i32,
)
});
Value::Null
}
pub fn set_damage_type(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let dt = n(args, 1) as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.hurt[s].damage_type = dt;
}
});
Value::Null
}
pub fn set_damage_type_typed(e: i32, dt: i32) {
set_damage_type(&[Value::Number(e as f64), Value::Number(dt as f64)]);
}
pub fn set_immunity(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let mask = n(args, 1) as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.immune[s] = mask;
}
});
Value::Null
}
pub fn set_immunity_typed(e: i32, mask: i32) {
set_immunity(&[Value::Number(e as f64), Value::Number(mask as f64)]);
}
pub fn set_weakness(args: &[Value]) -> Value {
with_world(|w| w.set_weakness(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_weakness_typed(e: i32, mask: i32) {
with_world(|w| w.set_weakness(e, mask));
}
pub fn set_grabber(args: &[Value]) -> Value {
with_world(|w| w.set_grabber(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_grabber_typed(e: i32, target_tag: i32) {
with_world(|w| w.set_grabber(e, target_tag));
}
pub fn set_trap(args: &[Value]) -> Value {
with_world(|w| w.set_trap(n(args, 0) as i32));
Value::Null
}
pub fn set_trap_typed(e: i32) {
with_world(|w| w.set_trap(e));
}
pub fn set_carrier(args: &[Value]) -> Value {
with_world(|w| w.set_carrier(n(args, 0) as i32));
Value::Null
}
pub fn set_carrier_typed(e: i32) {
with_world(|w| w.set_carrier(e));
}
pub fn set_part(args: &[Value]) -> Value {
with_world(|w| w.set_follow(n(args, 0) as i32, FOLLOW_PART, n(args, 1) as i32, 0));
Value::Null
}
pub fn set_part_typed(e: i32, parent_id: i32) {
with_world(|w| w.set_follow(e, FOLLOW_PART, parent_id, 0));
}
pub fn set_train(args: &[Value]) -> Value {
with_world(|w| w.set_follow(n(args, 0) as i32, FOLLOW_TRAIN, n(args, 1) as i32, 0));
Value::Null
}
pub fn set_train_typed(e: i32, head_id: i32) {
with_world(|w| w.set_follow(e, FOLLOW_TRAIN, head_id, 0));
}
pub fn set_orbiter(args: &[Value]) -> Value {
with_world(|w| {
w.set_follow(
n(args, 0) as i32,
FOLLOW_ORBIT,
n(args, 1) as i32,
n(args, 2) as i32,
)
});
Value::Null
}
pub fn set_orbiter_typed(e: i32, center_id: i32, radius: i32) {
with_world(|w| w.set_follow(e, FOLLOW_ORBIT, center_id, radius));
}
pub fn set_boomerang(args: &[Value]) -> Value {
with_world(|w| w.set_boomerang(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_boomerang_typed(e: i32, return_frames: i32) {
with_world(|w| w.set_boomerang(e, return_frames));
}
pub fn boomerang_caught(_args: &[Value]) -> Value {
Value::Number(with_world(|w| {
let c = w.boomer_catches;
w.boomer_catches = 0;
c
}) as f64)
}
pub fn boomerang_caught_typed() -> i32 {
with_world(|w| {
let c = w.boomer_catches;
w.boomer_catches = 0;
c
})
}
pub fn set_ambusher(args: &[Value]) -> Value {
with_world(|w| {
w.set_ambusher(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
)
});
Value::Null
}
pub fn set_ambusher_typed(e: i32, hide: i32, surface: i32, speed_q8: i32) {
with_world(|w| w.set_ambusher(e, hide, surface, speed_q8));
}
pub fn set_drifter(args: &[Value]) -> Value {
with_world(|w| {
w.set_drifter(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
)
});
Value::Null
}
pub fn set_drifter_typed(e: i32, rest: i32, fly: i32, speed_q8: i32) {
with_world(|w| w.set_drifter(e, rest, fly, speed_q8));
}
pub fn set_flicker_caster(args: &[Value]) -> Value {
with_world(|w| {
w.set_flicker_caster(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
to_fixed(n(args, 3)),
)
});
Value::Null
}
pub fn set_flicker_caster_typed(e: i32, hide: i32, vis: i32, shot_speed: Fixed) {
with_world(|w| w.set_flicker_caster(e, hide, vis, shot_speed));
}
pub fn set_bouncer(args: &[Value]) -> Value {
with_world(|w| {
w.set_bouncer(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
)
});
Value::Null
}
pub fn set_bouncer_typed(e: i32, rest: i32, hop: i32, speed_q8: i32) {
with_world(|w| w.set_bouncer(e, rest, hop, speed_q8));
}
pub fn set_ricochet(args: &[Value]) -> Value {
with_world(|w| w.set_ricochet(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_ricochet_typed(e: i32, speed_q8: i32) {
with_world(|w| w.set_ricochet(e, speed_q8));
}
pub fn entity_phased(args: &[Value]) -> Value {
Value::Number(entity_phased_typed(n(args, 0) as i32) as f64)
}
pub fn entity_phased_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.map(|s| (w.mask2[s] & (M2_PHASED | M2_HIDDEN) != 0) as i32)
.unwrap_or(0)
})
}
pub fn set_phased(args: &[Value]) -> Value {
with_world(|w| w.set_phased(n(args, 0) as i32, n(args, 1) as i32 != 0));
Value::Null
}
pub fn set_phased_typed(e: i32, on: i32) {
with_world(|w| w.set_phased(e, on != 0));
}
pub fn set_hit_proxy(args: &[Value]) -> Value {
with_world(|w| w.set_hit_proxy(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_hit_proxy_typed(e: i32, target: i32) {
with_world(|w| w.set_hit_proxy(e, target));
}
pub fn set_vuln_gate(args: &[Value]) -> Value {
with_world(|w| w.set_vuln_gate(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_vuln_gate_typed(e: i32, open: i32) {
with_world(|w| w.set_vuln_gate(e, open));
}
pub fn set_death_note(args: &[Value]) -> Value {
with_world(|w| w.set_death_note(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_death_note_typed(e: i32, code: i32) {
with_world(|w| w.set_death_note(e, code));
}
pub fn death_note(_args: &[Value]) -> Value {
Value::Number(death_note_typed() as f64)
}
pub fn death_note_typed() -> i32 {
with_world(|w| {
if w.death_notes.is_empty() {
0
} else {
w.death_notes.remove(0)
}
})
}
pub fn detach_part(args: &[Value]) -> Value {
with_world(|w| w.detach_part(n(args, 0) as i32));
Value::Null
}
pub fn detach_part_typed(e: i32) {
with_world(|w| w.detach_part(e));
}
pub fn set_dir_anim_typed(e: i32, base: i32, stride: i32, frames: i32, speed: i32) {
with_world(|w| w.set_dir_anim(e, base, stride, frames, speed));
}
pub fn bullet_damage_type(args: &[Value]) -> Value {
let dt = n(args, 0) as i32;
with_world(|w| w.bullet_style.damage_type = dt);
Value::Null
}
pub fn bullet_damage_type_typed(dt: i32) {
bullet_damage_type(&[Value::Number(dt as f64)]);
}
pub fn entity_immunity(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| w.slot_of(e).map(|s| w.immune[s]).unwrap_or(0)) as f64)
}
pub fn entity_immunity_typed(e: i32) -> i32 {
with_world(|w| w.slot_of(e).map(|s| w.immune[s]).unwrap_or(0))
}
pub fn bullet_style(args: &[Value]) -> Value {
with_world(|w| {
w.set_bullet_style(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
n(args, 4) as i32,
n(args, 5) as i32,
n(args, 6) as i32,
)
});
Value::Null
}
pub fn bullet_style_typed(
sheet: i32,
frame: i32,
size: i32,
damage: i32,
target: i32,
tag: i32,
ttl: i32,
) {
with_world(|w| w.set_bullet_style(sheet, frame, size, damage, target, tag, ttl));
}
pub fn fire_bullet(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.fire_bullet(
to_fixed(n(args, 0)),
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
to_fixed(n(args, 3)),
)
}) as f64)
}
pub fn fire_bullet_typed(cx: Fixed, cy: Fixed, vx: Fixed, vy: Fixed) -> i32 {
with_world(|w| w.fire_bullet(cx, cy, vx, vy))
}
pub fn fire_angle(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.fire_angle(
to_fixed(n(args, 0)),
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
to_fixed(n(args, 3)),
)
}) as f64)
}
pub fn fire_angle_typed(cx: Fixed, cy: Fixed, deg: Fixed, speed: Fixed) -> i32 {
with_world(|w| w.fire_angle(cx, cy, deg, speed))
}
pub fn fire_ring(args: &[Value]) -> Value {
with_world(|w| {
w.fire_ring(
to_fixed(n(args, 0)),
to_fixed(n(args, 1)),
n(args, 2) as i32,
to_fixed(n(args, 3)),
)
});
Value::Null
}
pub fn fire_ring_typed(cx: Fixed, cy: Fixed, count: i32, speed: Fixed) {
with_world(|w| w.fire_ring(cx, cy, count, speed));
}
pub fn fire_spread(args: &[Value]) -> Value {
with_world(|w| {
w.fire_spread(
to_fixed(n(args, 0)),
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
n(args, 3) as i32,
to_fixed(n(args, 4)),
to_fixed(n(args, 5)),
)
});
Value::Null
}
pub fn fire_spread_typed(
cx: Fixed,
cy: Fixed,
center_deg: Fixed,
count: i32,
spread_deg: Fixed,
speed: Fixed,
) {
with_world(|w| w.fire_spread(cx, cy, center_deg, count, spread_deg, speed));
}
pub fn fire_aimed(args: &[Value]) -> Value {
Value::Number(with_world(|w| {
w.fire_aimed(
to_fixed(n(args, 0)),
to_fixed(n(args, 1)),
to_fixed(n(args, 2)),
to_fixed(n(args, 3)),
to_fixed(n(args, 4)),
)
}) as f64)
}
pub fn fire_aimed_typed(cx: Fixed, cy: Fixed, tox: Fixed, toy: Fixed, speed: Fixed) -> i32 {
with_world(|w| w.fire_aimed(cx, cy, tox, toy, speed))
}
pub fn set_mover(args: &[Value]) -> Value {
with_world(|w| {
w.set_mover(
n(args, 0) as i32,
n(args, 1) as u8,
to_fixed(n(args, 2)),
to_fixed(n(args, 3)),
n(args, 4) as i32,
)
});
Value::Null
}
pub fn damage(args: &[Value]) -> Value {
Value::Bool(with_world(|w| {
w.damage(n(args, 0) as i32, n(args, 1) as i32)
}))
}
pub fn heal(args: &[Value]) -> Value {
with_world(|w| w.heal(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn set_hp(args: &[Value]) -> Value {
with_world(|w| {
let e = n(args, 0) as i32;
let hp = n(args, 1) as i32;
if let Some(s) = w.slot_of(e).filter(|&s| w.has(s, C_HEALTH)) {
let m = w.health[s].max;
let v = hp.clamp(0, m);
w.health[s].hp = v;
w.health[s].dead = v <= 0;
}
});
Value::Null
}
pub fn health_hp(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_HEALTH))
.map(|s| w.health[s].hp)
.unwrap_or(0)
}) as f64)
}
pub fn health_max(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_HEALTH))
.map(|s| w.health[s].max)
.unwrap_or(0)
}) as f64)
}
pub fn health_alive(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_HEALTH))
.map(|s| w.health[s].hp > 0)
.unwrap_or(false)
}))
}
pub fn entity_on_screen(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e).map(|s| w.on_screen(s)).unwrap_or(false)
}))
}
pub fn set_tag(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let t = n(args, 1) as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.tag[s] = t;
}
});
Value::Null
}
pub fn entity_tag(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| w.slot_of(e).map(|s| w.tag[s]).unwrap_or(0)) as f64)
}
pub fn cvar(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.cvar(n(args, 0) as i32, n(args, 1) as usize)) as f64)
}
pub fn cvar_typed(e: i32, k: i32) -> i32 {
with_world(|w| w.cvar(e, k.max(0) as usize))
}
pub fn set_cvar(args: &[Value]) -> Value {
with_world(|w| w.set_cvar(n(args, 0) as i32, n(args, 1) as usize, n(args, 2) as i32));
Value::Null
}
pub fn set_cvar_typed(e: i32, k: i32, v: i32) {
with_world(|w| w.set_cvar(e, k.max(0) as usize, v));
}
pub fn grid_setup(args: &[Value]) -> Value {
with_world(|w| w.grid_setup(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn grid_set_solid(args: &[Value]) -> Value {
with_world(|w| w.grid_set_solid(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) != 0.0));
Value::Null
}
pub fn grid_from_map(_args: &[Value]) -> Value {
let grid = tish_agb::native_map_solid_grid();
let oneway = tish_agb::native_map_oneway_grid();
let ladder = tish_agb::native_map_ladder_grid();
if let Some((solid, width, height)) = grid {
with_world(|w| {
w.grid_setup(width, height);
for row in 0..height {
let base = (row * width) as usize;
for col in 0..width {
let i = base + col as usize;
if let Some(l) = ladder {
if l[i] != 0 {
w.grid_set_ladder(col, row, true);
}
}
if oneway.map(|o| o[i] != 0).unwrap_or(false) {
w.grid_set_oneway(col, row, true);
} else if solid[i] != 0 {
w.grid_set_solid(col, row, true);
}
}
}
});
}
Value::Null
}
fn read_i32_array(v: &Value) -> Vec<i32> {
match v {
Value::Array(_) => {}
_ => return Vec::new(),
}
let len = match get_prop(v, "length") {
Value::Number(n) => n as usize,
_ => 0,
};
let mut out = Vec::with_capacity(len);
let mut i = 0;
while i < len {
out.push(
match tishlang_runtime_gba::get_index(v, &Value::Number(i as f64)) {
Value::Number(n) => n as i32,
_ => 0,
},
);
i += 1;
}
out
}
pub fn grid_from_gids(args: &[Value]) -> Value {
let width = n(args, 0) as i32;
let height = n(args, 1) as i32;
if width <= 0 || height <= 0 {
return Value::Null;
}
let data = match args.get(2) {
Some(v) => read_i32_array(v),
None => return Value::Null,
};
let solid = args.get(3).map(read_i32_array).unwrap_or_default();
let oneway = args.get(4).map(read_i32_array).unwrap_or_default();
let ladder = args.get(5).map(read_i32_array).unwrap_or_default();
with_world(|w| {
w.grid_setup(width, height);
for row in 0..height {
let base = (row * width) as usize;
for col in 0..width {
let gid = match data.get(base + col as usize) {
Some(g) => *g,
None => continue,
};
if ladder.contains(&gid) {
w.grid_set_ladder(col, row, true);
}
if oneway.contains(&gid) {
w.grid_set_oneway(col, row, true);
} else if solid.contains(&gid) {
w.grid_set_solid(col, row, true);
}
}
}
});
Value::Null
}
pub fn grid_set_oneway(args: &[Value]) -> Value {
with_world(|w| w.grid_set_oneway(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) != 0.0));
Value::Null
}
pub fn grid_set_ladder(args: &[Value]) -> Value {
with_world(|w| w.grid_set_ladder(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) != 0.0));
Value::Null
}
pub fn tile_ladder(args: &[Value]) -> Value {
let col = n(args, 0) as i32;
let row = n(args, 1) as i32;
Value::Bool(with_world(|w| w.is_ladder(col, row)))
}
pub fn attach_grid(args: &[Value]) -> Value {
with_world(|w| w.attach_grid(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn grid_step(args: &[Value]) -> Value {
with_world(|w| w.grid_step(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn grid_moving(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Bool(with_world(|w| {
w.slot_of(e)
.map(|s| w.has(s, C_GRIDPOS) && w.gridpos[s].moving)
.unwrap_or(false)
}))
}
pub fn grid_interact(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let call = with_world(|w| w.collect_interact(e));
if let Some((cb, data, target, actor)) = call {
value_call(
&cb,
&[
data,
Value::Number(target as f64),
Value::Number(actor as f64),
],
);
}
Value::Null
}
pub fn topdown_interact(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let reach = n(args, 1) as i32;
let call = with_world(|w| w.collect_topdown_interact(e, reach));
if let Some((cb, data, target, actor)) = call {
value_call(
&cb,
&[
data,
Value::Number(target as f64),
Value::Number(actor as f64),
],
);
return Value::Number(1.0);
}
Value::Number(0.0)
}
pub fn platformer_interact(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let reach = n(args, 1) as i32;
let call = with_world(|w| w.collect_platformer_interact(e, reach));
if let Some((cb, data, target, actor)) = call {
value_call(
&cb,
&[
data,
Value::Number(target as f64),
Value::Number(actor as f64),
],
);
return Value::Number(1.0);
}
Value::Number(0.0)
}
pub fn platformer_can_interact(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let reach = n(args, 1) as i32;
let found = with_world(|w| w.collect_platformer_interact(e, reach).is_some());
Value::Number(if found { 1.0 } else { 0.0 })
}
pub fn set_anim(args: &[Value]) -> Value {
with_world(|w| w.set_anim(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn set_walk(args: &[Value]) -> Value {
with_world(|w| w.set_walk(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
fn truthy(args: &[Value], i: usize) -> bool {
match args.get(i) {
Some(Value::Bool(b)) => *b,
Some(Value::Number(x)) => *x != 0.0,
_ => false,
}
}
pub fn anim_play(args: &[Value]) -> Value {
with_world(|w| {
w.anim_play(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
truthy(args, 4),
)
});
Value::Null
}
pub fn grid_facing(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.grid_facing(n(args, 0) as i32)) as f64)
}
pub fn grid_col(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.grid_col(n(args, 0) as i32)) as f64)
}
pub fn grid_row(args: &[Value]) -> Value {
Value::Number(with_world(|w| w.grid_row(n(args, 0) as i32)) as f64)
}
pub fn entity_sprite(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
Value::Number(with_world(|w| w.slot_of(e).map(|s| w.sprite[s].handle).unwrap_or(-1)) as f64)
}
pub fn attach_sprite(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let h = n(args, 1) as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.sprite[s] = SpriteRef {
handle: h,
ox: 0,
oy: 0,
};
w.mask[s] |= C_SPRITE;
w.used |= C_SPRITE;
}
});
tish_agb::native_sprite_release(h);
Value::Null
}
pub fn set_sprite_offset(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let ox = n(args, 1) as i32;
let oy = n(args, 2) as i32;
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.sprite[s].ox = ox;
w.sprite[s].oy = oy;
}
});
Value::Null
}
pub fn entity_x(args: &[Value]) -> Value {
with_world(|w| {
w.slot_of(n(args, 0) as i32)
.map(|s| Value::Number(from_fixed(w.transform[s].x)))
.unwrap_or(Value::Null)
})
}
pub fn entity_y(args: &[Value]) -> Value {
with_world(|w| {
w.slot_of(n(args, 0) as i32)
.map(|s| Value::Number(from_fixed(w.transform[s].y)))
.unwrap_or(Value::Null)
})
}
pub fn spawn_typed() -> i32 {
with_world(|w| w.spawn())
}
pub fn despawn_typed(e: i32) {
with_world(|w| w.despawn(e));
}
pub fn reset_entity_typed(e: i32) {
with_world(|w| w.reset_entity(e));
}
pub fn set_dynamic_typed(
e: i32,
diameter: Fixed,
restitution: i32,
friction: i32,
rest_speed: Fixed,
rank: i32,
) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.collider[s] = Collider {
w: diameter,
h: diameter,
};
let rv = rest_speed.to_raw() >> 4;
w.dynamic[s] = Dynamic {
restitution: restitution.clamp(0, 256),
friction: friction.clamp(0, 256),
rest_v2: rv * rv,
rank: rank.clamp(0, 255) as u8,
asleep: 0,
last_hit: 0,
};
w.mask[s] |= C_DYNAMIC | C_CIRCLE | C_COLLIDER | C_BODY | C_TRANSFORM;
w.used |= C_DYNAMIC | C_CIRCLE | C_COLLIDER | C_BODY | C_TRANSFORM;
}
});
}
pub fn body_impulse_typed(e: i32, turn: i32, speed: Fixed) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
let a = Fixed::from_raw(turn.rem_euclid(256));
let (c, si) = (a.cos(), a.sin());
w.body[s].vx += Fixed::from_raw((speed.to_raw() * c.to_raw()) >> 8);
w.body[s].vy += Fixed::from_raw((speed.to_raw() * si.to_raw()) >> 8);
w.dynamic[s].asleep = 0;
}
});
}
pub fn body_kick_typed(e: i32, from_x: Fixed, from_y: Fixed, speed: Fixed) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
let (cx, cy) = w.center_of(s);
let dx = (cx.to_raw() - from_x.to_raw()) >> 8;
let dy = (cy.to_raw() - from_y.to_raw()) >> 8;
let d2 = (dx * dx + dy * dy).max(1);
let mut len = 1i32;
while len * len < d2 {
len += 1;
}
let nx = (dx << 8) / len;
let ny = (dy << 8) / len;
w.body[s].vx += Fixed::from_raw((speed.to_raw() * nx) >> 8);
w.body[s].vy += Fixed::from_raw((speed.to_raw() * ny) >> 8);
w.dynamic[s].asleep = 0;
}
});
}
pub fn body_asleep_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.map(|s| w.dynamic[s].asleep as i32)
.unwrap_or(1)
})
}
pub fn body_speed2_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.map(|s| {
let (vx, vy) = (w.body[s].vx.to_raw(), w.body[s].vy.to_raw());
((vx >> 4) * (vx >> 4)) + ((vy >> 4) * (vy >> 4))
})
.unwrap_or(0)
})
}
pub fn body_last_hit_typed(e: i32) -> i32 {
with_world(|w| w.slot_of(e).map(|s| w.dynamic[s].last_hit).unwrap_or(0))
}
pub fn grid_set_surface_typed(col: i32, row: i32, id: i32) {
with_world(|w| w.grid_set_surface(col, row, id));
}
pub fn surface_def_typed(id: i32, ax: Fixed, ay: Fixed, friction: i32) {
with_world(|w| {
let i = (id.clamp(0, 15)) as usize;
w.surf[i] = SurfaceDef {
ax,
ay,
friction: friction.clamp(0, 256),
};
});
}
pub fn pool_new_typed(count: i32, sheet: i32, ox: i32, oy: i32) -> i32 {
with_world(|w| w.pool_new(count, sheet, ox, oy))
}
pub fn pool_arm_typed(p: i32, slot: i32, kind: i32, ttl: i32) -> i32 {
with_world(|w| w.pool_arm(p, slot, kind, ttl))
}
pub fn pool_retire_typed(p: i32, slot: i32) {
with_world(|w| w.pool_retire(p, slot));
}
pub fn pool_clear_typed(p: i32) {
with_world(|w| w.pool_clear(p));
}
pub fn pool_get_typed(p: i32, slot: i32, field: i32) -> i32 {
with_world(|w| w.pool_get(p, slot, field))
}
pub fn pool_stat_typed(p: i32, field: i32) -> i32 {
with_world(|w| w.pool_stat(p, field))
}
pub fn set_stun_typed(e: i32, frames: i32) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.stun[s] = frames.max(0);
}
});
}
pub fn is_stunned_typed(e: i32) -> i32 {
with_world(|w| w.slot_of(e).map(|s| (w.stun[s] > 0) as i32).unwrap_or(0))
}
pub fn set_transform_typed(e: i32, x: Fixed, y: Fixed) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.transform[s] = Transform { x, y };
w.mask[s] |= C_TRANSFORM;
w.used |= C_TRANSFORM;
}
});
}
pub fn set_body_typed(e: i32, vx: Fixed, vy: Fixed) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.body[s] = Body { vx, vy };
w.mask[s] |= C_BODY;
w.used |= C_BODY;
}
});
}
pub fn set_collider_typed(e: i32, cw: Fixed, ch: Fixed) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.collider[s] = Collider { w: cw, h: ch };
w.mask[s] |= C_COLLIDER;
w.used |= C_COLLIDER;
}
});
}
pub fn set_sprite_offset_typed(e: i32, ox: i32, oy: i32) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.sprite[s].ox = ox;
w.sprite[s].oy = oy;
}
});
}
pub fn attach_sprite_typed(e: i32, h: i32) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.sprite[s] = SpriteRef {
handle: h,
ox: 0,
oy: 0,
};
w.mask[s] |= C_SPRITE;
w.used |= C_SPRITE;
}
});
tish_agb::native_sprite_release(h);
}
pub fn set_tag_typed(e: i32, t: i32) {
with_world(|w| {
if let Some(s) = w.slot_of(e) {
w.tag[s] = t;
}
});
}
pub fn set_hurt_typed(e: i32, damage: i32, target_tag: i32, despawn_on_hit: i32, stun: i32) {
with_world(|w| w.set_hurt(e, damage, target_tag, despawn_on_hit != 0, stun));
}
pub fn set_lure_typed(e: i32, radius: i32, frames: i32) {
with_world(|w| w.set_lure(e, radius, frames));
}
pub fn set_shooter_typed(e: i32, interval: i32, speed: Fixed, aimed: i32) {
with_world(|w| w.set_shooter(e, interval, speed, aimed != 0));
}
pub fn set_charger_typed(e: i32, speed: i32, band: i32) {
with_world(|w| w.set_charger(e, speed, band));
}
pub fn topdown_interact_typed(e: i32, reach: i32) -> i32 {
match topdown_interact(&[Value::Number(e as f64), Value::Number(reach as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn entity_hp_typed(e: i32) -> i32 {
with_world(|w| match w.slot_of(e) {
Some(s) if w.has(s, C_HEALTH) => w.health[s].hp,
_ => 0,
})
}
pub fn entity_hp_max_typed(e: i32) -> i32 {
with_world(|w| match w.slot_of(e) {
Some(s) if w.has(s, C_HEALTH) => w.health[s].max,
_ => 0,
})
}
pub fn entity_alive_typed(e: i32) -> i32 {
with_world(|w| match w.slot_of(e) {
Some(s) if w.alive[s] => 1,
_ => 0,
})
}
pub fn set_lifetime_typed(e: i32, ttl: i32) {
with_world(|w| w.set_lifetime(e, ttl));
}
pub fn set_despawn_offscreen_typed(e: i32, on: i32) {
with_world(|w| w.set_despawn_offscreen(e, on != 0));
}
pub fn set_arena_wrap_typed(on: i32) {
with_world(|w| w.set_arena_wrap(on != 0));
}
pub fn set_mover_typed(e: i32, pattern: i32, vy: Fixed, amp: Fixed, period: i32) {
with_world(|w| w.set_mover(e, pattern as u8, vy, amp, period));
}
pub fn set_health_typed(e: i32, max: i32, invuln: i32) {
with_world(|w| w.set_health(e, max, invuln));
}
pub fn damage_typed(e: i32, amount: i32) {
with_world(|w| w.damage(e, amount));
}
pub fn entity_x_typed(e: i32) -> Fixed {
with_world(|w| {
w.slot_of(e)
.map(|s| w.transform[s].x)
.unwrap_or(Fixed::from_raw(0))
})
}
pub fn entity_y_typed(e: i32) -> Fixed {
with_world(|w| {
w.slot_of(e)
.map(|s| w.transform[s].y)
.unwrap_or(Fixed::from_raw(0))
})
}
pub fn entity_sprite_typed(e: i32) -> i32 {
with_world(|w| w.slot_of(e).map(|s| w.sprite[s].handle).unwrap_or(-1))
}
pub fn health_hp_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_HEALTH))
.map(|s| w.health[s].hp)
.unwrap_or(0)
})
}
pub fn health_max_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_HEALTH))
.map(|s| w.health[s].max)
.unwrap_or(0)
})
}
pub fn topdown_move_typed(e: i32, dx: i32, dy: i32) {
with_world(|w| w.topdown_move(e, dx, dy));
}
pub fn topdown_speed_typed(e: i32, px: i32) {
with_world(|w| w.topdown_speed(e, px as f64));
}
pub fn topdown_facing_typed(e: i32) -> i32 {
with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_TOPDOWN))
.map(|s| w.topdown[s].facing)
.unwrap_or(0)
})
}
pub fn anim_play_typed(e: i32, from: i32, len: i32, speed: i32, looping: bool) {
with_world(|w| w.anim_play(e, from, len, speed, looping));
}
pub fn define_component(args: &[Value]) -> Value {
let name = name_of(args, 0);
let config = args.get(1).cloned().unwrap_or(Value::Null);
with_world(|w| w.define_component(name, &config));
Value::Null
}
pub fn add_behaviour(args: &[Value]) -> Value {
let e = n(args, 0) as i32;
let name = name_of(args, 1);
let data = args.get(2).cloned().unwrap_or(Value::Null);
with_world(|w| {
if let (Some(s), Some(def)) = (w.slot_of(e), w.def_index_by_name(&name)) {
w.behaviour[s] = Some(BehaviourInstance {
def,
data,
started: false,
});
}
});
Value::Null
}
pub fn set_camera_target(args: &[Value]) -> Value {
with_world(|w| w.set_camera_target(n(args, 0) as i32));
Value::Null
}
#[no_mangle]
pub fn camera_transitioning(_args: &[Value]) -> Value {
Value::Bool(with_world(|w| {
w.room_cam.enabled && w.room_cam.transitioning
}))
}
#[no_mangle]
pub fn set_room_camera(args: &[Value]) -> Value {
with_world(|w| w.set_room_camera(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
fn timer_now() -> u32 {
match tish_agb::timer_read(&[]) {
Value::Number(n) => n as u32,
_ => 0,
}
}
static STEP_TICKS: SingleCore<RefCell<[u32; 5]>> = SingleCore::new(RefCell::new([0; 5]));
pub fn step_ticks(args: &[Value]) -> Value {
let i = (n(args, 0) as usize).min(4);
Value::Number(STEP_TICKS.with(|c| c.borrow()[i]) as f64)
}
static STEP_PEAK: SingleCore<RefCell<[u32; 5]>> = SingleCore::new(RefCell::new([0; 5]));
static FRAME_PERIOD: SingleCore<RefCell<[u32; 5]>> = SingleCore::new(RefCell::new([0; 5]));
pub fn frame_period(args: &[Value]) -> Value {
let mode = n(args, 0) as i32;
Value::Number(FRAME_PERIOD.with(|c| {
let p = c.borrow();
match mode {
1 => p[2],
2 => p[4],
_ => p[3],
}
}) as f64)
}
pub fn step_peak(args: &[Value]) -> Value {
let i = (n(args, 0) as usize).min(4);
Value::Number(STEP_PEAK.with(|c| c.borrow()[i]) as f64)
}
pub fn step_peak_reset(_args: &[Value]) -> Value {
STEP_PEAK.with(|c| *c.borrow_mut() = [0; 5]);
FRAME_PERIOD.with(|c| {
let mut p = c.borrow_mut();
p[1] = 0; p[2] = 0; p[4] = 0; });
Value::Null
}
pub fn entity_count(_args: &[Value]) -> Value {
Value::Number(with_world(|w| w.alive.iter().filter(|&&a| a).count()) as f64)
}
pub fn stun_all(args: &[Value]) -> Value {
Value::Number(stun_all_typed(n(args, 0) as i32, n(args, 1) as i32) as f64)
}
pub fn stun_all_typed(tag: i32, frames: i32) -> i32 {
with_world(|w| {
let mut hit = 0;
for s in 0..w.alive.len() {
if w.alive[s] && w.tag.get(s).copied().unwrap_or(0) == tag {
w.stun[s] = w.stun[s].max(frames);
hit += 1;
}
}
hit
})
}
pub fn nearest_tag(args: &[Value]) -> Value {
let r = with_world(|w| w.nearest_tag(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Number(r as f64)
}
pub fn nearest_tag_typed(e: i32, tag: i32, radius: i32) -> i32 {
with_world(|w| w.nearest_tag(e, tag, radius))
}
pub fn entity_dist(args: &[Value]) -> Value {
let r = with_world(|w| w.entity_dist(n(args, 0) as i32, n(args, 1) as i32));
Value::Number(r as f64)
}
pub fn entity_dist_typed(a: i32, b: i32) -> i32 {
with_world(|w| w.entity_dist(a, b))
}
pub fn entity_count_tag(args: &[Value]) -> Value {
let want = match args.first() {
Some(Value::Number(n)) => *n as i32,
_ => 0,
};
Value::Number(with_world(|w| {
let mut n = 0;
for (i, &a) in w.alive.iter().enumerate() {
if a && w.tag.get(i).copied().unwrap_or(0) == want {
n += 1;
}
}
n
}) as f64)
}
pub fn entity_slots(_args: &[Value]) -> Value {
Value::Number(with_world(|w| w.alive.len()) as f64)
}
const SYS_N: usize = 32;
static SYS_NAMES: [&str; SYS_N] = [
"patrol",
"mover",
"boomerang",
"hopper",
"jumper",
"movement",
"grid",
"platformer",
"chase",
"soldier",
"seek",
"shooter",
"charger",
"trap",
"wanderer",
"nai",
"topdown",
"dynamic",
"wrap",
"follow",
"diranim",
"grabber",
"combat",
"life",
"health",
"anim",
"walk",
"room_free",
"room_trans",
"fog",
"render",
"camera",
];
static SYS_PROF: SingleCore<RefCell<bool>> = SingleCore::new(RefCell::new(false));
static SYS_TICKS: SingleCore<RefCell<[u32; SYS_N]>> = SingleCore::new(RefCell::new([0; SYS_N]));
fn ts(w: &mut World, prof: bool, i: usize, need: u32, need2: u32, f: fn(&mut World)) {
if (need != 0 || need2 != 0) && (w.used & need) == 0 && (w.used2 & need2) == 0 {
return;
}
if !prof {
f(w);
return;
}
let t0 = timer_now();
f(w);
let dt = timer_now().wrapping_sub(t0) & 0xFFFF;
SYS_TICKS.with(|c| c.borrow_mut()[i] += dt);
}
pub fn sys_prof(args: &[Value]) -> Value {
SYS_PROF.with(|c| *c.borrow_mut() = n(args, 0) as i32 != 0);
Value::Null
}
pub fn sys_prof_typed(on: i32) {
SYS_PROF.with(|c| *c.borrow_mut() = on != 0);
}
pub fn sys_ticks(args: &[Value]) -> Value {
let i = (n(args, 0) as usize).min(SYS_N - 1);
Value::Number(SYS_TICKS.with(|c| c.borrow()[i]) as f64)
}
pub fn sys_ticks_typed(i: i32) -> i32 {
SYS_TICKS.with(|c| c.borrow()[(i as usize).min(SYS_N - 1)]) as i32
}
pub fn sys_count(_args: &[Value]) -> Value {
Value::Number(SYS_N as f64)
}
pub fn sys_count_typed() -> i32 {
SYS_N as i32
}
pub fn sys_name(args: &[Value]) -> Value {
let i = (n(args, 0) as usize).min(SYS_N - 1);
Value::String(SYS_NAMES[i].into())
}
pub fn world_step(_args: &[Value]) -> Value {
{
let now = timer_now();
FRAME_PERIOD.with(|c| {
let mut p = c.borrow_mut();
let period = now.wrapping_sub(p[0]) & 0xFFFF;
p[0] = now;
if p[1] != 0 {
p[3] = period; if period > p[2] {
p[2] = period; }
p[4] = if p[4] == 0 {
period
} else {
(p[4] as i32 + (period as i32 - p[4] as i32) / 16) as u32
};
}
p[1] = 1;
});
}
let work_start = timer_now();
let num_updates = with_world(|w| {
w.collect_behaviours();
w.buf_updates.len()
});
for i in 0..num_updates {
let (callback, data, entity) = with_world(|w| w.buf_updates[i].clone());
value_call(
&callback,
&[
data,
Value::Number(entity as f64),
Value::Number(1.0),
Value::native(set_hopper),
Value::native(set_jumper),
],
);
}
let num_ticks = with_world(|w| {
w.collect_ticks();
w.buf_ticks.len()
});
if num_ticks > 0 {
with_world(|w| w.buf_results.clear());
for i in 0..num_ticks {
let job = with_world(|w| w.buf_ticks[i].clone());
if job.lean {
value_call(&job.cb, &[Value::Number(job.entity as f64)]);
continue;
}
set_prop(&job.data, "e", Value::Number(job.entity as f64));
set_prop(&job.data, "x", Value::Number(job.x as f64));
set_prop(&job.data, "y", Value::Number(job.y as f64));
if job.platformer {
set_prop(&job.data, "grounded", Value::Bool(job.grounded));
set_prop(&job.data, "blocked", Value::Bool(job.blocked));
set_prop(&job.data, "move", Value::Number(0.0));
set_prop(&job.data, "jump", Value::Bool(false));
set_prop(&job.data, "jumpCut", Value::Bool(false));
set_prop(&job.data, "run", Value::Bool(false));
set_prop(&job.data, "drop", Value::Bool(false));
set_prop(&job.data, "flip", Value::Bool(false));
set_prop(&job.data, "bounce", Value::Number(0.0));
}
if job.body {
set_prop(&job.data, "vx", Value::Number(job.vx));
set_prop(&job.data, "vy", Value::Number(job.vy));
}
value_call(&job.cb, core::slice::from_ref(&job.data));
let mut out = TickOut::default();
if job.platformer {
out.move_dir = prop_num(&job.data, "move") as i32;
out.jump = prop_truthy(&job.data, "jump");
out.jump_cut = prop_truthy(&job.data, "jumpCut");
out.run = prop_truthy(&job.data, "run");
out.drop = prop_truthy(&job.data, "drop");
out.flip = prop_truthy(&job.data, "flip");
out.bounce = prop_num(&job.data, "bounce") as i32;
}
if job.body {
out.vx = prop_num(&job.data, "vx");
out.vy = prop_num(&job.data, "vy");
}
with_world(|w| w.buf_results.push((job.entity, out)));
}
with_world(|w| {
for j in 0..w.buf_results.len() {
let (e, out) = {
let tuple = &w.buf_results[j];
(tuple.0, tuple.1)
};
w.apply_tick(e, &out);
}
});
}
let t_p1 = timer_now();
let prof = SYS_PROF.with(|c| *c.borrow());
if prof {
SYS_TICKS.with(|c| *c.borrow_mut() = [0; SYS_N]);
}
with_world(|w| {
ts(w, prof, 0, C_PATROL, 0, World::patrol_system); ts(w, prof, 1, C_MOVER, 0, World::mover_system); ts(w, prof, 2, C_BOOMERANG, 0, World::boomerang_system);
ts(w, prof, 4, C_JUMPER, 0, World::jumper_system);
ts(w, prof, 5, C_BODY, 0, World::movement_system);
ts(w, prof, 6, C_GRIDPOS, 0, World::grid_system);
ts(
w,
prof,
7,
C_PLATFORMER,
M2_CARRIER,
World::platformer_system,
);
ts(w, prof, 8, C_CHASE, 0, World::chase_system); ts(w, prof, 3, C_HOPPER, 0, World::hopper_system);
ts(w, prof, 9, C_SOLDIER, 0, World::soldier_system);
ts(w, prof, 10, C_SEEK, 0, World::seek_system);
ts(w, prof, 11, C_SHOOTER, 0, World::shooter_system); ts(w, prof, 12, C_CHARGER, 0, World::charger_system); ts(w, prof, 13, C_TRAP, 0, World::trap_system); ts(w, prof, 14, 0, M2_WANDERER, World::wanderer_system); ts(w, prof, 15, 0, M2_NAI, World::nai_system); ts(w, prof, 16, C_TOPDOWN, 0, World::topdown_system); ts(w, prof, 17, C_DYNAMIC, 0, World::dynamic_system);
ts(w, prof, 18, 0, 0, World::wrap_system);
ts(w, prof, 19, C_FOLLOW, 0, World::follow_system);
ts(w, prof, 20, C_DIRANIM, 0, World::diranim_system); ts(w, prof, 21, C_GRABBER, 0, World::grabber_system); ts(w, prof, 22, C_HURT, 0, World::combat_system);
ts(w, prof, 23, C_LIFE, 0, World::life_system);
});
let t_p2 = timer_now();
let collisions = with_world(|w| w.collect_collisions());
for (callback, data, me, other) in collisions {
value_call(
&callback,
&[data, Value::Number(me as f64), Value::Number(other as f64)],
);
}
let deaths = with_world(|w| w.collect_deaths());
for (callback, data, entity) in deaths {
if matches!(callback, Value::Null) {
let pooled = with_world(|w| {
w.slot_of(entity)
.map(|s| w.pool_of[s] >= 0)
.unwrap_or(false)
});
if !pooled {
with_world(|w| w.despawn(entity));
}
} else {
value_call(&callback, &[data, Value::Number(entity as f64)]);
}
}
let t_p3 = timer_now();
with_world(|w| {
ts(w, prof, 24, C_HEALTH, 0, World::health_system);
ts(w, prof, 25, C_ANIM, 0, World::anim_system);
ts(w, prof, 26, C_WALK, 0, World::walk_system);
ts(w, prof, 27, 0, 0, World::room_track_free); ts(w, prof, 28, 0, 0, World::room_transition_system); ts(w, prof, 29, 0, 0, World::fog_system);
ts(w, prof, 30, C_SPRITE, 0, World::render_system);
ts(w, prof, 31, 0, 0, World::update_camera);
});
let work_end = timer_now();
let d = |a: u32, b: u32| b.wrapping_sub(a) & 0xFFFF;
let now = [
d(work_start, work_end),
d(work_start, t_p1),
d(t_p1, t_p2),
d(t_p2, t_p3),
d(t_p3, work_end),
];
STEP_TICKS.with(|c| *c.borrow_mut() = now);
STEP_PEAK.with(|c| {
let mut p = c.borrow_mut();
for i in 0..5 {
if now[i] > p[i] {
p[i] = now[i];
}
}
});
tish_agb::frame(&[]);
Value::Null
}
#[derive(Clone, Copy)]
struct IsoBoardCell {
height: u8, tile: u8, walkable: bool,
occupant: i32, }
const COST_TURN: i32 = 500;
const COST_MOVE: i32 = 300;
const COST_ACTION: i32 = 200;
const RESERVE_MAX: i32 = 500;
#[derive(Clone, Copy)]
struct IsoBoardUnit {
col: i32,
row: i32,
team: u8,
speed: u16,
mov: u8,
jump: u8,
hp: i16,
max_hp: i16,
ct: i32,
alive: bool,
flying: bool,
speed_scale: u16,
}
struct IsoBoardGrid {
pub w: i32,
pub h: i32,
cells: Vec<IsoBoardCell>,
units: Vec<IsoBoardUnit>,
in_move: Vec<bool>, parent: Vec<i32>, reach: Vec<(i16, i16)>, start: (i32, i32), path: Vec<(i16, i16)>, cost: [u8; 256],
dist: Vec<i32>,
zoc: Vec<bool>,
zoc_on: bool,
}
impl IsoBoardGrid {
const fn new() -> Self {
IsoBoardGrid {
w: 0,
h: 0,
cells: Vec::new(),
units: Vec::new(),
in_move: Vec::new(),
parent: Vec::new(),
reach: Vec::new(),
start: (0, 0),
path: Vec::new(),
cost: [1u8; 256],
dist: Vec::new(),
zoc: Vec::new(),
zoc_on: false,
}
}
fn idx(&self, c: i32, r: i32) -> Option<usize> {
if c >= 0 && r >= 0 && c < self.w && r < self.h {
Some((r * self.w + c) as usize)
} else {
None
}
}
fn init(&mut self, w: i32, h: i32) {
self.w = w.max(0);
self.h = h.max(0);
let n = (self.w * self.h) as usize;
self.cells =
alloc::vec![IsoBoardCell { height: 0, tile: 0, walkable: true, occupant: -1 }; n];
self.in_move = alloc::vec![false; n];
self.parent = alloc::vec![-1i32; n];
self.dist = alloc::vec![i32::MAX; n];
self.zoc = alloc::vec![false; n];
self.reach.clear();
self.path.clear();
self.units.clear();
}
fn add_unit(
&mut self,
col: i32,
row: i32,
team: u8,
speed: u16,
mov: u8,
jump: u8,
hp: i16,
) -> i32 {
let id = self.units.len() as i32;
self.units.push(IsoBoardUnit {
col,
row,
team,
speed,
mov,
jump,
hp,
max_hp: hp,
ct: 0,
alive: true,
flying: false,
speed_scale: 100,
});
if let Some(i) = self.idx(col, row) {
self.cells[i].occupant = id;
}
id
}
fn unit_set_pos(&mut self, id: i32, c: i32, r: i32) {
let (oc, or) = match self.units.get(id as usize) {
Some(u) => (u.col, u.row),
None => return,
};
if let Some(i) = self.idx(oc, or) {
if self.cells[i].occupant == id {
self.cells[i].occupant = -1;
}
}
if let Some(i) = self.idx(c, r) {
self.cells[i].occupant = id;
}
if let Some(u) = self.units.get_mut(id as usize) {
u.col = c;
u.row = r;
}
}
fn tick_speed(u: &IsoBoardUnit) -> i32 {
(((u.speed as i32) * (u.speed_scale as i32)) / 100).max(1)
}
fn turn_next(&mut self) -> i32 {
const THRESH: i32 = 1000;
let mut best_t = i32::MAX;
for u in &self.units {
if !u.alive {
continue;
}
let sp = Self::tick_speed(u);
let t = (THRESH - u.ct + sp - 1) / sp; if t < best_t {
best_t = t;
}
}
if best_t == i32::MAX {
return -1;
}
for i in 0..self.units.len() {
if self.units[i].alive {
let sp = Self::tick_speed(&self.units[i]);
self.units[i].ct = (self.units[i].ct + best_t * sp).min(THRESH + RESERVE_MAX);
}
}
let mut who = -1i32;
let mut hi = i32::MIN;
for (i, u) in self.units.iter().enumerate() {
if u.alive && u.ct >= THRESH && u.ct > hi {
hi = u.ct;
who = i as i32;
}
}
if who >= 0 {
self.units[who as usize].ct -= COST_TURN;
}
who
}
fn move_range(&mut self, sc: i32, sr: i32, budget: i32, jump: i32, flying: bool, team: i32) {
let n = self.cells.len();
for i in 0..n {
self.in_move[i] = false;
self.parent[i] = -1;
self.dist[i] = i32::MAX;
self.zoc[i] = false;
}
if self.zoc_on && team >= 0 {
for u in 0..self.units.len() {
let e = self.units[u];
if !e.alive || e.team as i32 == team {
continue;
}
const AROUND: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, 1), (0, -1)];
for (dc, dr) in AROUND {
if let Some(i) = self.idx(e.col + dc, e.row + dr) {
self.zoc[i] = true;
}
}
}
}
self.reach.clear();
self.start = (sc, sr);
let start = match self.idx(sc, sr) {
Some(i) => i,
None => return,
};
self.dist[start] = 0;
self.in_move[start] = true;
self.reach.push((sc as i16, sr as i16));
for d in 0..budget {
for cur in 0..n {
if self.dist[cur] != d {
continue;
}
if cur != start && self.zoc[cur] {
continue; }
let cc = (cur as i32) % self.w;
let cr = (cur as i32) / self.w;
let ch = self.cells[cur].height as i32;
const DIRS: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, 1), (0, -1)];
for (dc, dr) in DIRS {
let (nc, nr) = (cc + dc, cr + dr);
let ni = match self.idx(nc, nr) {
Some(i) => i,
None => continue,
};
let cell = self.cells[ni];
if !cell.walkable || cell.occupant != -1 {
continue; }
if !flying && (cell.height as i32 - ch).abs() > jump {
continue; }
let step = if flying {
1
} else {
self.cost[cell.tile as usize] as i32
};
let nd = d + step;
if nd > budget || nd >= self.dist[ni] {
continue;
}
if self.dist[ni] == i32::MAX {
self.reach.push((nc as i16, nr as i16));
}
self.dist[ni] = nd;
self.parent[ni] = cur as i32;
self.in_move[ni] = true;
}
}
}
}
fn knock_dest(&self, id: i32, sc: i32, sr: i32) -> Option<(i32, i32, i32)> {
let u = match self.units.get(id as usize) {
Some(u) if u.alive => *u,
_ => return None,
};
let (dc, dr) = (u.col - sc, u.row - sr);
if dc == 0 && dr == 0 {
return None; }
let (sx, sy) = if dc.abs() >= dr.abs() {
(if dc >= 0 { 1 } else { -1 }, 0)
} else {
(0, if dr >= 0 { 1 } else { -1 })
};
let (nc, nr) = (u.col + sx, u.row + sy);
let ni = self.idx(nc, nr)?;
if !self.cells[ni].walkable || self.cells[ni].occupant != -1 {
return None;
}
let here = self.idx(u.col, u.row)?;
Some((
nc,
nr,
self.cells[here].height as i32 - self.cells[ni].height as i32,
))
}
fn path_to(&mut self, tc: i32, tr: i32) {
self.path.clear();
let ti = match self.idx(tc, tr) {
Some(i) if self.in_move[i] => i,
_ => return,
};
let mut chain: Vec<(i16, i16)> = Vec::new();
let mut cur = ti as i32;
while cur >= 0 {
let c = cur % self.w;
let r = cur / self.w;
chain.push((c as i16, r as i16));
let p = self.parent[cur as usize];
if cur == p {
break;
}
cur = p;
}
chain.reverse(); self.path = chain;
}
}
static ISO_GRID: SingleCore<RefCell<IsoBoardGrid>> =
SingleCore::new(RefCell::new(IsoBoardGrid::new()));
fn with_iso_grid<R>(f: impl FnOnce(&mut IsoBoardGrid) -> R) -> R {
ISO_GRID.with(|c| f(&mut c.borrow_mut()))
}
pub struct IsoBoard {
pub bg: i32,
pub w: i32,
pub h: i32,
pub ox: i32,
pub oy: i32,
pub lift: i32,
pub frames: &'static [u8],
pub heights: &'static [u8],
pub walk: &'static [u8],
pub stack_off: &'static [u16],
pub stack_elev: &'static [u8],
pub stack_tile: &'static [u8],
pub spawns: &'static [(u8, u8, u8, u8)], pub mapw: i32,
pub maph: i32,
pub cw: i32,
pub ch: i32,
pub sky: &'static [u16],
}
const MAX_BOARDS: usize = 256;
static ISO_BOARDS: SingleCore<RefCell<[Option<&'static IsoBoard>; MAX_BOARDS]>> =
SingleCore::new(RefCell::new([None; MAX_BOARDS]));
static ISO_BOARDS_N: SingleCore<RefCell<usize>> = SingleCore::new(RefCell::new(0));
pub fn native_isoboard_register_bg(board: &'static IsoBoard, bg: i32) -> i32 {
native_isoboard_register_bg2(board, bg, -1)
}
pub fn native_isoboard_register_bg2(board: &'static IsoBoard, bg: i32, fg: i32) -> i32 {
let idx = native_isoboard_register(board);
if idx >= 0 {
ISO_BOARD_BG.with(|c| c.borrow_mut()[idx as usize] = bg);
ISO_BOARD_FG.with(|c| c.borrow_mut()[idx as usize] = fg);
}
idx
}
static ISO_BOARD_BG: SingleCore<RefCell<[i32; MAX_BOARDS]>> =
SingleCore::new(RefCell::new([0; MAX_BOARDS]));
static ISO_BOARD_FG: SingleCore<RefCell<[i32; MAX_BOARDS]>> =
SingleCore::new(RefCell::new([-1; MAX_BOARDS]));
pub fn native_isoboard_register(board: &'static IsoBoard) -> i32 {
ISO_BOARDS_N.with(|n| {
let mut n = n.borrow_mut();
if *n >= MAX_BOARDS {
return -1;
}
let idx = *n as i32;
ISO_BOARDS.with(|c| c.borrow_mut()[*n] = Some(board));
*n += 1;
idx
})
}
fn board_stack_span(b: &IsoBoard, col: i32, row: i32) -> Option<(usize, usize)> {
if col < 0 || row < 0 || col >= b.w || row >= b.h {
return None;
}
let idx = (row * b.w + col) as usize;
let start = *b.stack_off.get(idx)? as usize;
let end = *b.stack_off.get(idx + 1)? as usize;
Some((start, end - start))
}
pub fn isob_stack_count(args: &[Value]) -> Value {
let (h, c, r) = (n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32);
Value::Number(
with_board(h, |b| {
board_stack_span(b, c, r).map_or(0, |(_, len)| len as i32)
})
.unwrap_or(0) as f64,
)
}
fn stack_field(args: &[Value], f: impl Fn(&IsoBoard, usize) -> i32) -> Value {
let (h, c, r, i) = (
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as usize,
);
Value::Number(
with_board(h, |b| match board_stack_span(b, c, r) {
Some((start, len)) if i < len => f(b, start + i),
_ => 0,
})
.unwrap_or(0) as f64,
)
}
pub fn isob_stack_elev(args: &[Value]) -> Value {
stack_field(args, |b, j| b.stack_elev[j] as i32)
}
pub fn isob_stack_tile(args: &[Value]) -> Value {
stack_field(args, |b, j| b.stack_tile[j] as i32)
}
fn with_board<R>(handle: i32, f: impl FnOnce(&IsoBoard) -> R) -> Option<R> {
if handle < 0 || handle as usize >= MAX_BOARDS {
return None;
}
ISO_BOARDS.with(|c| c.borrow()[handle as usize].map(f))
}
pub fn isob_load(args: &[Value]) -> Value {
let handle = n(args, 0) as i32;
if let Some((w, h, frames, heights, walk, sky)) =
with_board(handle, |b| (b.w, b.h, b.frames, b.heights, b.walk, b.sky))
{
tish_agb::native_sky_set(sky);
with_iso_grid(|t| {
t.init(w, h);
for i in 0..(w * h) as usize {
if let Some(ci) = t.idx(i as i32 % w, i as i32 / w) {
t.cells[ci].height = *heights.get(i).unwrap_or(&0);
t.cells[ci].tile = *frames.get(i).unwrap_or(&0);
t.cells[ci].walkable = *walk.get(i).unwrap_or(&1) != 0;
}
}
});
}
Value::Null
}
pub fn isob_board_bg(args: &[Value]) -> Value {
let h = n(args, 0) as i32;
if h < 0 || h as usize >= MAX_BOARDS {
return Value::Number(-1.0);
}
Value::Number(ISO_BOARD_BG.with(|c| c.borrow()[h as usize]) as f64)
}
pub fn isob_board_mapw(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.mapw).unwrap_or(512) as f64)
}
pub fn isob_board_maph(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.maph).unwrap_or(512) as f64)
}
pub fn isob_board_cw(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.cw).unwrap_or(512) as f64)
}
pub fn isob_board_ch(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.ch).unwrap_or(512) as f64)
}
pub fn isob_board_fg(args: &[Value]) -> Value {
let h = n(args, 0) as i32;
if h < 0 || h as usize >= MAX_BOARDS {
return Value::Number(-1.0);
}
Value::Number(ISO_BOARD_FG.with(|c| c.borrow()[h as usize]) as f64)
}
pub fn isob_board_ox(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.ox).unwrap_or(96) as f64)
}
pub fn isob_board_oy(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.oy).unwrap_or(24) as f64)
}
pub fn isob_board_lift(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.lift).unwrap_or(8) as f64)
}
pub fn isob_w(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.w) as f64)
}
pub fn isob_h(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.h) as f64)
}
pub fn isob_spawn_count(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.spawns.len() as i32).unwrap_or(0) as f64)
}
fn spawn_field(args: &[Value], f: impl Fn(&(u8, u8, u8, u8)) -> i32) -> Value {
let (h, i) = (n(args, 0) as i32, n(args, 1) as usize);
Value::Number(with_board(h, |b| b.spawns.get(i).map(&f).unwrap_or(0)).unwrap_or(0) as f64)
}
pub fn isob_spawn_col(args: &[Value]) -> Value {
spawn_field(args, |s| s.0 as i32)
}
pub fn isob_spawn_row(args: &[Value]) -> Value {
spawn_field(args, |s| s.1 as i32)
}
pub fn isob_spawn_cls(args: &[Value]) -> Value {
spawn_field(args, |s| s.2 as i32)
}
pub fn isob_spawn_team(args: &[Value]) -> Value {
spawn_field(args, |s| s.3 as i32)
}
pub fn isob_init(args: &[Value]) -> Value {
with_iso_grid(|t| t.init(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn isob_set_cell(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(i) = t.idx(n(args, 0) as i32, n(args, 1) as i32) {
t.cells[i].height = n(args, 2) as u8;
t.cells[i].tile = n(args, 3) as u8;
t.cells[i].walkable = n(args, 4) != 0.0;
}
});
Value::Null
}
pub fn isob_height(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.cells[i].height as i32)
}) as f64)
}
pub fn isob_tile(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.cells[i].tile as i32)
}) as f64)
}
pub fn isob_walkable(args: &[Value]) -> Value {
Value::Bool(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.is_some_and(|i| t.cells[i].walkable)
}))
}
pub fn isob_set_occupant(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(i) = t.idx(n(args, 0) as i32, n(args, 1) as i32) {
t.cells[i].occupant = n(args, 2) as i32;
}
});
Value::Null
}
pub fn isob_occupant(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(-1, |i| t.cells[i].occupant)
}) as f64)
}
pub fn isob_move_range(args: &[Value]) -> Value {
let flying = n(args, 4) != 0.0;
let team = if args.len() > 5 {
n(args, 5) as i32
} else {
-1
};
with_iso_grid(|t| {
t.move_range(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
flying,
team,
)
});
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn isob_move_cost(args: &[Value]) -> Value {
let (c, r) = (n(args, 0) as i32, n(args, 1) as i32);
Value::Number(with_iso_grid(|t| match t.idx(c, r) {
Some(i) if t.in_move[i] => t.dist[i],
_ => -1,
}) as f64)
}
pub fn isob_in_range(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.in_move[i] as i32)
}) as f64)
}
pub fn isob_range_count(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn isob_range_col(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.reach
.get(n(args, 0) as usize)
.map_or(-1, |&(c, _)| c as i32)
}) as f64)
}
pub fn isob_range_row(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.reach
.get(n(args, 0) as usize)
.map_or(-1, |&(_, r)| r as i32)
}) as f64)
}
pub fn isob_path(args: &[Value]) -> Value {
with_iso_grid(|t| t.path_to(n(args, 0) as i32, n(args, 1) as i32));
Value::Number(with_iso_grid(|t| t.path.len()) as f64)
}
pub fn isob_path_len(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.path.len()) as f64)
}
pub fn isob_path_col(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.path
.get(n(args, 0) as usize)
.map_or(-1, |&(c, _)| c as i32)
}) as f64)
}
pub fn isob_path_row(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.path
.get(n(args, 0) as usize)
.map_or(-1, |&(_, r)| r as i32)
}) as f64)
}
pub fn isob_add_unit(args: &[Value]) -> Value {
let id = with_iso_grid(|t| {
t.add_unit(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as u8,
n(args, 3) as u16,
n(args, 4) as u8,
n(args, 5) as u8,
n(args, 6) as i16,
)
});
Value::Number(id as f64)
}
pub fn isob_clear_units(_args: &[Value]) -> Value {
with_iso_grid(|t| {
t.units.clear();
for c in t.cells.iter_mut() {
c.occupant = -1;
}
});
Value::Null
}
pub fn isob_unit_count(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.units.len()) as f64)
}
fn unit_field(args: &[Value], f: impl Fn(&IsoBoardUnit) -> i32) -> Value {
Value::Number(with_iso_grid(|t| t.units.get(n(args, 0) as usize).map_or(-1, f)) as f64)
}
pub fn isob_unit_col(args: &[Value]) -> Value {
unit_field(args, |u| u.col)
}
pub fn isob_unit_row(args: &[Value]) -> Value {
unit_field(args, |u| u.row)
}
pub fn isob_unit_team(args: &[Value]) -> Value {
unit_field(args, |u| u.team as i32)
}
pub fn isob_unit_hp(args: &[Value]) -> Value {
unit_field(args, |u| u.hp as i32)
}
pub fn isob_unit_maxhp(args: &[Value]) -> Value {
unit_field(args, |u| u.max_hp as i32)
}
pub fn isob_unit_move(args: &[Value]) -> Value {
unit_field(args, |u| u.mov as i32)
}
pub fn isob_unit_jump(args: &[Value]) -> Value {
unit_field(args, |u| u.jump as i32)
}
pub fn isob_unit_speed(args: &[Value]) -> Value {
unit_field(args, |u| u.speed as i32)
}
pub fn isob_unit_alive(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.units
.get(n(args, 0) as usize)
.map_or(0, |u| u.alive as i32)
}) as f64)
}
pub fn isob_unit_move_range(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(u) = t.units.get(n(args, 0) as usize).copied() {
t.move_range(
u.col,
u.row,
u.mov as i32,
u.jump as i32,
u.flying,
u.team as i32,
);
}
});
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn isob_set_terrain_cost(args: &[Value]) -> Value {
let tile = n(args, 0) as usize;
let cost = (n(args, 1) as i32).clamp(1, 255) as u8;
with_iso_grid(|t| {
if tile < t.cost.len() {
t.cost[tile] = cost;
}
});
Value::Null
}
pub fn isob_knockback(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let (sc, sr) = (n(args, 1) as i32, n(args, 2) as i32);
let moved = with_iso_grid(|t| match t.knock_dest(id, sc, sr) {
Some((nc, nr, _)) => {
t.unit_set_pos(id, nc, nr);
true
}
None => false,
});
Value::Number(moved as i32 as f64)
}
pub fn isob_knock_drop(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let (sc, sr) = (n(args, 1) as i32, n(args, 2) as i32);
Value::Number(
with_iso_grid(|t| t.knock_dest(id, sc, sr).map_or(0, |(_, _, d)| d.max(0))) as f64,
)
}
pub fn isob_set_zoc(args: &[Value]) -> Value {
let on = n(args, 0) != 0.0;
with_iso_grid(|t| t.zoc_on = on);
Value::Null
}
pub fn isob_revive(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let hp = n(args, 1) as i16;
Value::Number(with_iso_grid(|t| {
let (col, row, max_hp) = match t.units.get(id as usize) {
Some(u) if !u.alive => (u.col, u.row, u.max_hp),
_ => return 0,
};
match t.idx(col, row) {
Some(i) if t.cells[i].occupant < 0 => t.cells[i].occupant = id,
_ => return 0,
}
if let Some(u) = t.units.get_mut(id as usize) {
u.alive = true;
u.hp = hp.clamp(1, max_hp);
}
1
}) as f64)
}
pub fn isob_turn_end(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let moved = n(args, 1) != 0.0;
let acted = n(args, 2) != 0.0;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
let mut cost = 0;
if moved {
cost += COST_MOVE;
}
if acted {
cost += COST_ACTION;
}
u.ct = (u.ct - cost).max(0);
}
});
Value::Null
}
pub fn isob_unit_ct(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
Value::Number(with_iso_grid(|t| t.units.get(id).map_or(0, |u| u.ct)) as f64)
}
pub fn isob_unit_set_speed_scale(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let pct = (n(args, 1) as i32).clamp(1, 1000) as u16;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
u.speed_scale = pct;
}
});
Value::Null
}
pub fn isob_unit_set_flying(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let f = n(args, 1) != 0.0;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
u.flying = f;
}
});
Value::Null
}
pub fn isob_unit_set_pos(args: &[Value]) -> Value {
with_iso_grid(|t| t.unit_set_pos(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn isob_damage(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let amt = n(args, 1) as i16;
with_iso_grid(|t| {
let (dead, c, r) = match t.units.get_mut(id as usize) {
Some(u) if u.alive => {
u.hp -= amt;
if u.hp <= 0 {
u.hp = 0;
u.alive = false;
}
(!u.alive, u.col, u.row)
}
_ => (false, 0, 0),
};
if dead {
if let Some(i) = t.idx(c, r) {
if t.cells[i].occupant == id {
t.cells[i].occupant = -1;
}
}
}
});
unit_field(args, |u| u.hp as i32)
}
pub fn isob_heal(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let amt = n(args, 1) as i16;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id as usize) {
if u.alive {
u.hp = (u.hp + amt).min(u.max_hp);
}
}
});
unit_field(args, |u| if u.alive { u.hp as i32 } else { 0 })
}
pub fn isob_unit_set_maxhp(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let v = (n(args, 1) as i16).max(1);
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id as usize) {
let delta = v - u.max_hp;
u.max_hp = v;
if delta > 0 {
u.hp += delta;
}
if u.hp > u.max_hp {
u.hp = u.max_hp;
}
}
});
unit_field(args, |u| u.max_hp as i32)
}
pub fn isob_turn_next(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.turn_next()) as f64)
}
impl IsoBoardGrid {
fn adjacent_enemy(&self, unit_id: i32) -> i32 {
let u = match self.units.get(unit_id as usize) {
Some(u) => *u,
None => return -1,
};
const DIRS: [(i32, i32); 4] = [(1, 0), (-1, 0), (0, 1), (0, -1)];
for (dc, dr) in DIRS {
if let Some(i) = self.idx(u.col + dc, u.row + dr) {
let occ = self.cells[i].occupant;
if occ >= 0 {
if let Some(other) = self.units.get(occ as usize) {
if other.alive && other.team != u.team {
return occ;
}
}
}
}
}
-1
}
}
pub fn isob_adjacent_enemy(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.adjacent_enemy(n(args, 0) as i32)) as f64)
}
pub fn isob_stack_count_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match isob_stack_count(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_stack_elev_typed(p0: i32, p1: i32, p2: i32, p3: i32) -> i32 {
match isob_stack_elev(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_stack_tile_typed(p0: i32, p1: i32, p2: i32, p3: i32) -> i32 {
match isob_stack_tile(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_load_typed(p0: i32) {
isob_load(&[Value::Number(p0 as f64)]);
}
pub fn isob_board_bg_typed(p0: i32) -> i32 {
match isob_board_bg(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_board_cw_typed(p0: i32) -> i32 {
match isob_board_cw(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn isob_board_ch_typed(p0: i32) -> i32 {
match isob_board_ch(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn isob_board_mapw_typed(p0: i32) -> i32 {
match isob_board_mapw(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn isob_board_maph_typed(p0: i32) -> i32 {
match isob_board_maph(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn isob_board_fg_typed(p0: i32) -> i32 {
match isob_board_fg(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => -1,
}
}
pub fn isob_board_ox_typed(p0: i32) -> i32 {
match isob_board_ox(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_board_oy_typed(p0: i32) -> i32 {
match isob_board_oy(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_board_lift_typed(p0: i32) -> i32 {
match isob_board_lift(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_w_typed() -> i32 {
match isob_w(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_h_typed() -> i32 {
match isob_h(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_spawn_count_typed(p0: i32) -> i32 {
match isob_spawn_count(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_spawn_col_typed(p0: i32, p1: i32) -> i32 {
match isob_spawn_col(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_spawn_row_typed(p0: i32, p1: i32) -> i32 {
match isob_spawn_row(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_spawn_cls_typed(p0: i32, p1: i32) -> i32 {
match isob_spawn_cls(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_spawn_team_typed(p0: i32, p1: i32) -> i32 {
match isob_spawn_team(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_init_typed(p0: i32, p1: i32) {
isob_init(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn isob_set_cell_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32) {
isob_set_cell(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
]);
}
pub fn isob_height_typed(p0: i32, p1: i32) -> i32 {
match isob_height(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_tile_typed(p0: i32, p1: i32) -> i32 {
match isob_tile(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_walkable_typed(p0: i32, p1: i32) -> i32 {
match isob_walkable(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_set_occupant_typed(p0: i32, p1: i32, p2: i32) {
isob_set_occupant(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn isob_occupant_typed(p0: i32, p1: i32) -> i32 {
match isob_occupant(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_move_range_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32) -> i32 {
match isob_move_range(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
Value::Number(p5 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_move_cost_typed(p0: i32, p1: i32) -> i32 {
match isob_move_cost(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_in_range_typed(p0: i32, p1: i32) -> i32 {
match isob_in_range(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_range_count_typed() -> i32 {
match isob_range_count(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_range_col_typed(p0: i32) -> i32 {
match isob_range_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_range_row_typed(p0: i32) -> i32 {
match isob_range_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_path_typed(p0: i32, p1: i32) -> i32 {
match isob_path(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_path_len_typed() -> i32 {
match isob_path_len(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_path_col_typed(p0: i32) -> i32 {
match isob_path_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_path_row_typed(p0: i32) -> i32 {
match isob_path_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_add_unit_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32, p6: i32) -> i32 {
match isob_add_unit(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
Value::Number(p5 as f64),
Value::Number(p6 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_clear_units_typed() {
isob_clear_units(&[]);
}
pub fn isob_unit_count_typed() -> i32 {
match isob_unit_count(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_col_typed(p0: i32) -> i32 {
match isob_unit_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_row_typed(p0: i32) -> i32 {
match isob_unit_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_team_typed(p0: i32) -> i32 {
match isob_unit_team(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_hp_typed(p0: i32) -> i32 {
match isob_unit_hp(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_maxhp_typed(p0: i32) -> i32 {
match isob_unit_maxhp(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_move_typed(p0: i32) -> i32 {
match isob_unit_move(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_jump_typed(p0: i32) -> i32 {
match isob_unit_jump(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_speed_typed(p0: i32) -> i32 {
match isob_unit_speed(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_alive_typed(p0: i32) -> i32 {
match isob_unit_alive(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_move_range_typed(p0: i32) -> i32 {
match isob_unit_move_range(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_set_terrain_cost_typed(p0: i32, p1: i32) {
isob_set_terrain_cost(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn isob_knockback_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match isob_knockback(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_knock_drop_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match isob_knock_drop(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_set_zoc_typed(p0: i32) {
isob_set_zoc(&[Value::Number(p0 as f64)]);
}
pub fn isob_revive_typed(p0: i32, p1: i32) -> i32 {
match isob_revive(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_turn_end_typed(p0: i32, p1: i32, p2: i32) {
isob_turn_end(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn isob_unit_ct_typed(p0: i32) -> i32 {
match isob_unit_ct(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_set_speed_scale_typed(p0: i32, p1: i32) {
isob_unit_set_speed_scale(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn isob_unit_set_flying_typed(p0: i32, p1: i32) {
isob_unit_set_flying(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn isob_unit_set_pos_typed(p0: i32, p1: i32, p2: i32) {
isob_unit_set_pos(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn isob_damage_typed(p0: i32, p1: i32) -> i32 {
match isob_damage(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_heal_typed(p0: i32, p1: i32) -> i32 {
match isob_heal(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_unit_set_maxhp_typed(p0: i32, p1: i32) -> i32 {
match isob_unit_set_maxhp(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_turn_next_typed() -> i32 {
match isob_turn_next(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn isob_adjacent_enemy_typed(p0: i32) -> i32 {
match isob_adjacent_enemy(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn entity_count_typed() -> i32 {
match entity_count(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn entity_count_tag_typed(p0: i32) -> i32 {
match entity_count_tag(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn entity_tag_typed(p0: i32) -> i32 {
match entity_tag(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn frame_period_typed(p0: i32) -> i32 {
match frame_period(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_col_typed(p0: i32) -> i32 {
match grid_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_facing_typed(p0: i32) -> i32 {
match grid_facing(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_from_map_typed() {
grid_from_map(&[]);
}
pub fn grid_interact_typed(p0: i32) -> i32 {
match grid_interact(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_moving_typed(p0: i32) -> i32 {
match grid_moving(&[Value::Number(p0 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_row_typed(p0: i32) -> i32 {
match grid_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn grid_set_ladder_typed(p0: i32, p1: i32, p2: i32) {
grid_set_ladder(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn grid_set_oneway_typed(p0: i32, p1: i32, p2: i32) {
grid_set_oneway(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn grid_set_solid_typed(p0: i32, p1: i32, p2: i32) {
grid_set_solid(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn grid_setup_typed(p0: i32, p1: i32) {
grid_setup(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn grid_step_typed(p0: i32, p1: i32, p2: i32) {
grid_step(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn heal_typed(p0: i32, p1: i32) {
heal(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn overlaps_typed(p0: i32, p1: i32) -> i32 {
match overlaps(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_blocked_typed(p0: i32) -> i32 {
match platformer_blocked(&[Value::Number(p0 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_bounce_typed(p0: i32, p1: i32) {
platformer_bounce(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn platformer_can_interact_typed(p0: i32, p1: i32) -> i32 {
match platformer_can_interact(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_drop_typed(p0: i32) {
platformer_drop(&[Value::Number(p0 as f64)]);
}
pub fn platformer_face_typed(p0: i32) -> i32 {
match platformer_face(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_grounded_typed(p0: i32) -> i32 {
match platformer_grounded(&[Value::Number(p0 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_hold_typed(p0: i32, p1: i32) {
platformer_hold(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn platformer_interact_typed(p0: i32, p1: i32) -> i32 {
match platformer_interact(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn platformer_jump_typed(p0: i32) {
platformer_jump(&[Value::Number(p0 as f64)]);
}
pub fn platformer_jump_release_typed(p0: i32) {
platformer_jump_release(&[Value::Number(p0 as f64)]);
}
pub fn platformer_run_typed(p0: i32) {
platformer_run(&[Value::Number(p0 as f64)]);
}
pub fn platformer_set_speed_typed(e: i32, walk: Fixed, run: Fixed) {
with_world(|w| w.platformer_set_speed(e, walk.to_raw(), run.to_raw()));
}
pub fn platformer_set_physics_typed(e: i32, jump: Fixed, grav: Fixed) {
with_world(|w| w.platformer_set_physics(e, jump.to_raw(), grav.to_raw()));
}
pub fn platformer_launch_typed(e: i32, vx: Fixed, vy: Fixed) {
with_world(|w| w.platformer_launch(e, vx.to_raw(), vy.to_raw()));
}
pub fn platformer_set_vy_typed(e: i32, vy: Fixed) {
with_world(|w| w.platformer_set_vy(e, vy.to_raw()));
}
pub fn platformer_vy_typed(e: i32) -> Fixed {
with_world(|w| {
w.slot_of(e)
.filter(|&s| w.has(s, C_PLATFORMER))
.map(|s| w.platformer[s].vy)
.unwrap_or(Fixed::from_raw(0))
})
}
pub fn platformer_walk_typed(p0: i32, p1: i32) {
platformer_walk(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn set_chase_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32) {
set_chase(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
]);
}
pub fn flow_goal_typed(p0: i32, p1: i32, p2: i32) {
with_world(|w| w.flow_goal(p0 as usize, p1, p2));
}
pub fn flow_dist_typed(p0: i32, p1: i32, p2: i32) -> i32 {
with_world(|w| w.flow_dist(p0 as usize, p1, p2))
}
pub fn set_seek_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32) {
with_world(|w| w.set_seek(p0, p1, p2, p3, p4));
}
pub fn clear_seek_typed(p0: i32) {
with_world(|w| w.clear_seek(p0));
}
pub fn seek_arrived_typed(p0: i32) -> i32 {
with_world(|w| w.seek_arrived(p0)) as i32
}
pub fn set_soldier_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32) {
with_world(|w| w.set_soldier(p0, p1, p2, p3, p4));
}
pub fn soldier_target_typed(p0: i32) -> i32 {
with_world(|w| w.soldier_target(p0))
}
pub fn soldier_team_typed(p0: i32) -> i32 {
with_world(|w| w.soldier_team(p0))
}
pub fn fog_init_typed(p0: i32, p1: i32) {
with_world(|w| w.fog_init(p0, p1));
}
pub fn set_sleeping_typed(p0: i32, p1: i32) {
set_sleeping(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn terrain_set_typed(p0: i32, p1: i32, p2: i32, p3: i32) {
with_world(|w| w.terrain_set(p0, p1, p2, p3));
}
pub fn terrain_blit_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32) -> i32 {
with_world(|w| w.terrain_blit(p0, p1, p2, p3, p4, p5))
}
pub fn set_vision_typed(p0: i32, p1: i32) {
with_world(|w| w.set_vision(p0, p1));
}
pub fn fog_reveal_typed(p0: i32, p1: i32, p2: i32) {
with_world(|w| w.fog_reveal(p0, p1, p2));
}
pub fn fog_state_typed(p0: i32, p1: i32) -> i32 {
match fog_state(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn fog_blit_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32, p6: i32) -> i32 {
with_world(|w| w.fog_blit(p0, p1, p2, p3, p4, p5, p6))
}
pub fn set_guard_typed(p0: i32, p1: i32) {
set_guard(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn set_hopper_typed(p0: i32, p1: i32) {
set_hopper(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn set_wanderer_typed(p0: i32, p1: i32) {
set_wanderer(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn topdown_speed_raw_typed(p0: i32, p1: i32) {
topdown_speed_raw(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn wanderer_wants_shot_typed(p0: i32) -> i32 {
match wanderer_wants_shot(&[Value::Number(p0 as f64)]) {
Value::Number(n) => n as i32,
_ => 0,
}
}
pub fn set_jumper_typed(p0: i32) {
set_jumper(&[Value::Number(p0 as f64)]);
}
pub fn step_ticks_typed(p0: i32) -> i32 {
match step_ticks(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn swing_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32) -> i32 {
match swing(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
Value::Number(p5 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn world_step_typed() -> i32 {
match world_step(&[]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_stack_count(args: &[Value]) -> Value {
let (h, c, r) = (n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32);
Value::Number(
with_board(h, |b| {
board_stack_span(b, c, r).map_or(0, |(_, len)| len as i32)
})
.unwrap_or(0) as f64,
)
}
pub fn iso_stack_elev(args: &[Value]) -> Value {
stack_field(args, |b, j| b.stack_elev[j] as i32)
}
pub fn iso_stack_tile(args: &[Value]) -> Value {
stack_field(args, |b, j| b.stack_tile[j] as i32)
}
pub fn iso_load(args: &[Value]) -> Value {
let handle = n(args, 0) as i32;
if let Some((w, h, frames, heights, walk, sky)) =
with_board(handle, |b| (b.w, b.h, b.frames, b.heights, b.walk, b.sky))
{
tish_agb::native_sky_set(sky);
with_iso_grid(|t| {
t.init(w, h);
for i in 0..(w * h) as usize {
if let Some(ci) = t.idx(i as i32 % w, i as i32 / w) {
t.cells[ci].height = *heights.get(i).unwrap_or(&0);
t.cells[ci].tile = *frames.get(i).unwrap_or(&0);
t.cells[ci].walkable = *walk.get(i).unwrap_or(&1) != 0;
}
}
});
}
Value::Null
}
pub fn iso_board_bg(args: &[Value]) -> Value {
let h = n(args, 0) as i32;
if h < 0 || h as usize >= MAX_BOARDS {
return Value::Number(-1.0);
}
Value::Number(ISO_BOARD_BG.with(|c| c.borrow()[h as usize]) as f64)
}
pub fn iso_board_mapw(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.mapw).unwrap_or(512) as f64)
}
pub fn iso_board_maph(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.maph).unwrap_or(512) as f64)
}
pub fn iso_board_cw(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.cw).unwrap_or(512) as f64)
}
pub fn iso_board_ch(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.ch).unwrap_or(512) as f64)
}
pub fn iso_board_fg(args: &[Value]) -> Value {
let h = n(args, 0) as i32;
if h < 0 || h as usize >= MAX_BOARDS {
return Value::Number(-1.0);
}
Value::Number(ISO_BOARD_FG.with(|c| c.borrow()[h as usize]) as f64)
}
pub fn iso_board_ox(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.ox).unwrap_or(96) as f64)
}
pub fn iso_board_oy(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.oy).unwrap_or(24) as f64)
}
pub fn iso_board_lift(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.lift).unwrap_or(8) as f64)
}
pub fn iso_w(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.w) as f64)
}
pub fn iso_h(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.h) as f64)
}
pub fn iso_spawn_count(args: &[Value]) -> Value {
Value::Number(with_board(n(args, 0) as i32, |b| b.spawns.len() as i32).unwrap_or(0) as f64)
}
pub fn iso_spawn_col(args: &[Value]) -> Value {
spawn_field(args, |s| s.0 as i32)
}
pub fn iso_spawn_row(args: &[Value]) -> Value {
spawn_field(args, |s| s.1 as i32)
}
pub fn iso_spawn_cls(args: &[Value]) -> Value {
spawn_field(args, |s| s.2 as i32)
}
pub fn iso_spawn_team(args: &[Value]) -> Value {
spawn_field(args, |s| s.3 as i32)
}
pub fn iso_init(args: &[Value]) -> Value {
with_iso_grid(|t| t.init(n(args, 0) as i32, n(args, 1) as i32));
Value::Null
}
pub fn iso_set_cell(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(i) = t.idx(n(args, 0) as i32, n(args, 1) as i32) {
t.cells[i].height = n(args, 2) as u8;
t.cells[i].tile = n(args, 3) as u8;
t.cells[i].walkable = n(args, 4) != 0.0;
}
});
Value::Null
}
pub fn iso_height(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.cells[i].height as i32)
}) as f64)
}
pub fn iso_tile(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.cells[i].tile as i32)
}) as f64)
}
pub fn iso_walkable(args: &[Value]) -> Value {
Value::Bool(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.is_some_and(|i| t.cells[i].walkable)
}))
}
pub fn iso_set_occupant(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(i) = t.idx(n(args, 0) as i32, n(args, 1) as i32) {
t.cells[i].occupant = n(args, 2) as i32;
}
});
Value::Null
}
pub fn iso_occupant(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(-1, |i| t.cells[i].occupant)
}) as f64)
}
pub fn iso_move_range(args: &[Value]) -> Value {
let flying = n(args, 4) != 0.0;
let team = if args.len() > 5 {
n(args, 5) as i32
} else {
-1
};
with_iso_grid(|t| {
t.move_range(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as i32,
n(args, 3) as i32,
flying,
team,
)
});
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn iso_move_cost(args: &[Value]) -> Value {
let (c, r) = (n(args, 0) as i32, n(args, 1) as i32);
Value::Number(with_iso_grid(|t| match t.idx(c, r) {
Some(i) if t.in_move[i] => t.dist[i],
_ => -1,
}) as f64)
}
pub fn iso_in_range(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.idx(n(args, 0) as i32, n(args, 1) as i32)
.map_or(0, |i| t.in_move[i] as i32)
}) as f64)
}
pub fn iso_range_count(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn iso_range_col(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.reach
.get(n(args, 0) as usize)
.map_or(-1, |&(c, _)| c as i32)
}) as f64)
}
pub fn iso_range_row(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.reach
.get(n(args, 0) as usize)
.map_or(-1, |&(_, r)| r as i32)
}) as f64)
}
pub fn iso_path(args: &[Value]) -> Value {
with_iso_grid(|t| t.path_to(n(args, 0) as i32, n(args, 1) as i32));
Value::Number(with_iso_grid(|t| t.path.len()) as f64)
}
pub fn iso_path_len(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.path.len()) as f64)
}
pub fn iso_path_col(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.path
.get(n(args, 0) as usize)
.map_or(-1, |&(c, _)| c as i32)
}) as f64)
}
pub fn iso_path_row(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.path
.get(n(args, 0) as usize)
.map_or(-1, |&(_, r)| r as i32)
}) as f64)
}
pub fn iso_add_unit(args: &[Value]) -> Value {
let id = with_iso_grid(|t| {
t.add_unit(
n(args, 0) as i32,
n(args, 1) as i32,
n(args, 2) as u8,
n(args, 3) as u16,
n(args, 4) as u8,
n(args, 5) as u8,
n(args, 6) as i16,
)
});
Value::Number(id as f64)
}
pub fn iso_clear_units(_args: &[Value]) -> Value {
with_iso_grid(|t| {
t.units.clear();
for c in t.cells.iter_mut() {
c.occupant = -1;
}
});
Value::Null
}
pub fn iso_unit_count(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.units.len()) as f64)
}
pub fn iso_unit_col(args: &[Value]) -> Value {
unit_field(args, |u| u.col)
}
pub fn iso_unit_row(args: &[Value]) -> Value {
unit_field(args, |u| u.row)
}
pub fn iso_unit_team(args: &[Value]) -> Value {
unit_field(args, |u| u.team as i32)
}
pub fn iso_unit_hp(args: &[Value]) -> Value {
unit_field(args, |u| u.hp as i32)
}
pub fn iso_unit_maxhp(args: &[Value]) -> Value {
unit_field(args, |u| u.max_hp as i32)
}
pub fn iso_unit_move(args: &[Value]) -> Value {
unit_field(args, |u| u.mov as i32)
}
pub fn iso_unit_jump(args: &[Value]) -> Value {
unit_field(args, |u| u.jump as i32)
}
pub fn iso_unit_speed(args: &[Value]) -> Value {
unit_field(args, |u| u.speed as i32)
}
pub fn iso_unit_alive(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| {
t.units
.get(n(args, 0) as usize)
.map_or(0, |u| u.alive as i32)
}) as f64)
}
pub fn iso_unit_move_range(args: &[Value]) -> Value {
with_iso_grid(|t| {
if let Some(u) = t.units.get(n(args, 0) as usize).copied() {
t.move_range(
u.col,
u.row,
u.mov as i32,
u.jump as i32,
u.flying,
u.team as i32,
);
}
});
Value::Number(with_iso_grid(|t| t.reach.len()) as f64)
}
pub fn iso_set_terrain_cost(args: &[Value]) -> Value {
let tile = n(args, 0) as usize;
let cost = (n(args, 1) as i32).clamp(1, 255) as u8;
with_iso_grid(|t| {
if tile < t.cost.len() {
t.cost[tile] = cost;
}
});
Value::Null
}
pub fn iso_knockback(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let (sc, sr) = (n(args, 1) as i32, n(args, 2) as i32);
let moved = with_iso_grid(|t| match t.knock_dest(id, sc, sr) {
Some((nc, nr, _)) => {
t.unit_set_pos(id, nc, nr);
true
}
None => false,
});
Value::Number(moved as i32 as f64)
}
pub fn iso_knock_drop(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let (sc, sr) = (n(args, 1) as i32, n(args, 2) as i32);
Value::Number(
with_iso_grid(|t| t.knock_dest(id, sc, sr).map_or(0, |(_, _, d)| d.max(0))) as f64,
)
}
pub fn iso_set_zoc(args: &[Value]) -> Value {
let on = n(args, 0) != 0.0;
with_iso_grid(|t| t.zoc_on = on);
Value::Null
}
pub fn iso_revive(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let hp = n(args, 1) as i16;
Value::Number(with_iso_grid(|t| {
let (col, row, max_hp) = match t.units.get(id as usize) {
Some(u) if !u.alive => (u.col, u.row, u.max_hp),
_ => return 0,
};
match t.idx(col, row) {
Some(i) if t.cells[i].occupant < 0 => t.cells[i].occupant = id,
_ => return 0,
}
if let Some(u) = t.units.get_mut(id as usize) {
u.alive = true;
u.hp = hp.clamp(1, max_hp);
}
1
}) as f64)
}
pub fn iso_turn_end(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let moved = n(args, 1) != 0.0;
let acted = n(args, 2) != 0.0;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
let mut cost = 0;
if moved {
cost += COST_MOVE;
}
if acted {
cost += COST_ACTION;
}
u.ct = (u.ct - cost).max(0);
}
});
Value::Null
}
pub fn iso_unit_ct(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
Value::Number(with_iso_grid(|t| t.units.get(id).map_or(0, |u| u.ct)) as f64)
}
pub fn iso_unit_set_speed_scale(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let pct = (n(args, 1) as i32).clamp(1, 1000) as u16;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
u.speed_scale = pct;
}
});
Value::Null
}
pub fn iso_unit_set_flying(args: &[Value]) -> Value {
let id = n(args, 0) as usize;
let f = n(args, 1) != 0.0;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id) {
u.flying = f;
}
});
Value::Null
}
pub fn iso_unit_set_pos(args: &[Value]) -> Value {
with_iso_grid(|t| t.unit_set_pos(n(args, 0) as i32, n(args, 1) as i32, n(args, 2) as i32));
Value::Null
}
pub fn iso_damage(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let amt = n(args, 1) as i16;
with_iso_grid(|t| {
let (dead, c, r) = match t.units.get_mut(id as usize) {
Some(u) if u.alive => {
u.hp -= amt;
if u.hp <= 0 {
u.hp = 0;
u.alive = false;
}
(!u.alive, u.col, u.row)
}
_ => (false, 0, 0),
};
if dead {
if let Some(i) = t.idx(c, r) {
if t.cells[i].occupant == id {
t.cells[i].occupant = -1;
}
}
}
});
unit_field(args, |u| u.hp as i32)
}
pub fn iso_heal(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let amt = n(args, 1) as i16;
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id as usize) {
if u.alive {
u.hp = (u.hp + amt).min(u.max_hp);
}
}
});
unit_field(args, |u| if u.alive { u.hp as i32 } else { 0 })
}
pub fn iso_unit_set_maxhp(args: &[Value]) -> Value {
let id = n(args, 0) as i32;
let v = (n(args, 1) as i16).max(1);
with_iso_grid(|t| {
if let Some(u) = t.units.get_mut(id as usize) {
let delta = v - u.max_hp;
u.max_hp = v;
if delta > 0 {
u.hp += delta;
}
if u.hp > u.max_hp {
u.hp = u.max_hp;
}
}
});
unit_field(args, |u| u.max_hp as i32)
}
pub fn iso_turn_next(_args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.turn_next()) as f64)
}
pub fn iso_adjacent_enemy(args: &[Value]) -> Value {
Value::Number(with_iso_grid(|t| t.adjacent_enemy(n(args, 0) as i32)) as f64)
}
pub fn iso_stack_count_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match iso_stack_count(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_stack_elev_typed(p0: i32, p1: i32, p2: i32, p3: i32) -> i32 {
match iso_stack_elev(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_stack_tile_typed(p0: i32, p1: i32, p2: i32, p3: i32) -> i32 {
match iso_stack_tile(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_load_typed(p0: i32) {
iso_load(&[Value::Number(p0 as f64)]);
}
pub fn iso_board_bg_typed(p0: i32) -> i32 {
match iso_board_bg(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_board_cw_typed(p0: i32) -> i32 {
match iso_board_cw(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn iso_board_ch_typed(p0: i32) -> i32 {
match iso_board_ch(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn iso_board_mapw_typed(p0: i32) -> i32 {
match iso_board_mapw(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn iso_board_maph_typed(p0: i32) -> i32 {
match iso_board_maph(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 512,
}
}
pub fn iso_board_fg_typed(p0: i32) -> i32 {
match iso_board_fg(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => -1,
}
}
pub fn iso_board_ox_typed(p0: i32) -> i32 {
match iso_board_ox(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_board_oy_typed(p0: i32) -> i32 {
match iso_board_oy(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_board_lift_typed(p0: i32) -> i32 {
match iso_board_lift(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_w_typed() -> i32 {
match iso_w(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_h_typed() -> i32 {
match iso_h(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_spawn_count_typed(p0: i32) -> i32 {
match iso_spawn_count(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_spawn_col_typed(p0: i32, p1: i32) -> i32 {
match iso_spawn_col(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_spawn_row_typed(p0: i32, p1: i32) -> i32 {
match iso_spawn_row(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_spawn_cls_typed(p0: i32, p1: i32) -> i32 {
match iso_spawn_cls(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_spawn_team_typed(p0: i32, p1: i32) -> i32 {
match iso_spawn_team(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_init_typed(p0: i32, p1: i32) {
iso_init(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn iso_set_cell_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32) {
iso_set_cell(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
]);
}
pub fn iso_height_typed(p0: i32, p1: i32) -> i32 {
match iso_height(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_tile_typed(p0: i32, p1: i32) -> i32 {
match iso_tile(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_walkable_typed(p0: i32, p1: i32) -> i32 {
match iso_walkable(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Bool(b) => b as i32,
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_set_occupant_typed(p0: i32, p1: i32, p2: i32) {
iso_set_occupant(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn iso_occupant_typed(p0: i32, p1: i32) -> i32 {
match iso_occupant(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_move_range_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32) -> i32 {
match iso_move_range(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
Value::Number(p5 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_move_cost_typed(p0: i32, p1: i32) -> i32 {
match iso_move_cost(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_in_range_typed(p0: i32, p1: i32) -> i32 {
match iso_in_range(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_range_count_typed() -> i32 {
match iso_range_count(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_range_col_typed(p0: i32) -> i32 {
match iso_range_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_range_row_typed(p0: i32) -> i32 {
match iso_range_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_path_typed(p0: i32, p1: i32) -> i32 {
match iso_path(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_path_len_typed() -> i32 {
match iso_path_len(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_path_col_typed(p0: i32) -> i32 {
match iso_path_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_path_row_typed(p0: i32) -> i32 {
match iso_path_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_add_unit_typed(p0: i32, p1: i32, p2: i32, p3: i32, p4: i32, p5: i32, p6: i32) -> i32 {
match iso_add_unit(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
Value::Number(p3 as f64),
Value::Number(p4 as f64),
Value::Number(p5 as f64),
Value::Number(p6 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_clear_units_typed() {
iso_clear_units(&[]);
}
pub fn iso_unit_count_typed() -> i32 {
match iso_unit_count(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_col_typed(p0: i32) -> i32 {
match iso_unit_col(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_row_typed(p0: i32) -> i32 {
match iso_unit_row(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_team_typed(p0: i32) -> i32 {
match iso_unit_team(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_hp_typed(p0: i32) -> i32 {
match iso_unit_hp(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_maxhp_typed(p0: i32) -> i32 {
match iso_unit_maxhp(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_move_typed(p0: i32) -> i32 {
match iso_unit_move(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_jump_typed(p0: i32) -> i32 {
match iso_unit_jump(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_speed_typed(p0: i32) -> i32 {
match iso_unit_speed(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_alive_typed(p0: i32) -> i32 {
match iso_unit_alive(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_move_range_typed(p0: i32) -> i32 {
match iso_unit_move_range(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_set_terrain_cost_typed(p0: i32, p1: i32) {
iso_set_terrain_cost(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn iso_knockback_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match iso_knockback(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_knock_drop_typed(p0: i32, p1: i32, p2: i32) -> i32 {
match iso_knock_drop(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_set_zoc_typed(p0: i32) {
iso_set_zoc(&[Value::Number(p0 as f64)]);
}
pub fn iso_revive_typed(p0: i32, p1: i32) -> i32 {
match iso_revive(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_turn_end_typed(p0: i32, p1: i32, p2: i32) {
iso_turn_end(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn iso_unit_ct_typed(p0: i32) -> i32 {
match iso_unit_ct(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_set_speed_scale_typed(p0: i32, p1: i32) {
iso_unit_set_speed_scale(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn iso_unit_set_flying_typed(p0: i32, p1: i32) {
iso_unit_set_flying(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]);
}
pub fn iso_unit_set_pos_typed(p0: i32, p1: i32, p2: i32) {
iso_unit_set_pos(&[
Value::Number(p0 as f64),
Value::Number(p1 as f64),
Value::Number(p2 as f64),
]);
}
pub fn iso_damage_typed(p0: i32, p1: i32) -> i32 {
match iso_damage(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_heal_typed(p0: i32, p1: i32) -> i32 {
match iso_heal(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_unit_set_maxhp_typed(p0: i32, p1: i32) -> i32 {
match iso_unit_set_maxhp(&[Value::Number(p0 as f64), Value::Number(p1 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_turn_next_typed() -> i32 {
match iso_turn_next(&[]) {
Value::Number(v) => v as i32,
_ => 0,
}
}
pub fn iso_adjacent_enemy_typed(p0: i32) -> i32 {
match iso_adjacent_enemy(&[Value::Number(p0 as f64)]) {
Value::Number(v) => v as i32,
_ => 0,
}
}