// Built-in script for the `transporter_*` archetypes (see builtin). // // A transporter is a solid, see-through, unpushable machine that teleports // whatever bumps into it from its facing side. It animates through a 4-frame // loop (500 ms/frame) and, on a front bump, moves the bumper either to the cell // just past it or — if that's blocked — out of the nearest opposite-facing // transporter further along its axis (skipping any whose entrance is blocked). // // The direction is not baked into this source: every transporter shares one // compiled copy and reads its `BUILTIN_transporter_` tag. The world is // reached through the global `Board`/`Player` constants. The bumper is moved by // coordinate via `shift([[from], [to]])`, which relocates whatever solid sits at // the entrance — player, object, or a pushed crate — onto the empty destination. // This transporter's facing unit vector, from its direction tag. fn facing(me) { if me.has_tag("BUILTIN_transporter_north") { [0, -1] } else if me.has_tag("BUILTIN_transporter_south") { [0, 1] } else if me.has_tag("BUILTIN_transporter_east") { [1, 0] } else { [-1, 0] } } // The tag of the opposite-facing transporter this one pairs with. fn opposite_tag(me) { if me.has_tag("BUILTIN_transporter_north") { "BUILTIN_transporter_south" } else if me.has_tag("BUILTIN_transporter_south") { "BUILTIN_transporter_north" } else if me.has_tag("BUILTIN_transporter_east") { "BUILTIN_transporter_west" } else { "BUILTIN_transporter_east" } } // The 4-frame animation loop for this direction. fn frames(me) { if me.has_tag("BUILTIN_transporter_north") { ["^", "-", "^", "~"] } else if me.has_tag("BUILTIN_transporter_south") { ["v", "_", "v", "-"] } else if me.has_tag("BUILTIN_transporter_east") { [")", "|", ")", ">"] } else { ["(", "|", "(", "<"] } } fn tick(me, dt) { // Advance one animation frame every 0.5s, like the spinner: frame state lives // in the board Registry (script scope resets each tick), keyed per instance. if me.waiting { return; } let fs = frames(me); let fkey = `xport_${me.id}`; let f = Board.registry.get_or(fkey, 0); set_tile(fs[f % 4]); Board.registry.set(fkey, (f + 1) % 4); me.delay(0.15); } // Move whatever solid sits at (sx, sy) onto the empty cell (tx, ty) immediately. // A two-cell `shift` relocates the source solid — player, object, or crate — and // leaves its old cell empty (the destination is checked empty by the caller). Kept // as a helper so the nested-array literal stays at a shallow expression depth. fn transport(sx, sy, tx, ty) { shift([[sx, sy], [tx, ty]]); now(); } fn bump(me, dir) { let d = facing(me); let dx = d[0]; let dy = d[1]; // Only transport things that hit us from the front — the entrance side (-facing). // `dir` is the side the bump came from, so its offset must be the opposite of our // facing; a bump from any other side does nothing. if dir.dx != -dx || dir.dy != -dy { return; } // Whatever solid is pressed against our entrance cell (me - d) gets moved: // the player, another object, or a pushed crate — all handled uniformly by // shifting the solid at that coordinate onto an empty destination. let ex = me.x - dx; let ey = me.y - dy; // 1. The cell just past us (the opposite side): if nothing solid is there, // drop the bumper onto it. let fx = me.x + dx; let fy = me.y + dy; if Board.passable(fx, fy) { transport(ex, ey, fx, fy); return; } // 2. Otherwise scan along our axis for the nearest opposite-facing transporter // whose far side (the cell just past it) is free, and drop the bumper there. // A blocked pair is skipped; give up at the board edge. let opp = Board.tagged(opposite_tag(me)); let cx = fx; let cy = fy; loop { cx += dx; cy += dy; if cx < 0 || cy < 0 || cx >= Board.width || cy >= Board.height { return; } // Is one of the opposite transporters sitting at (cx, cy)? let here = false; for o in opp { if o.x == cx && o.y == cy { here = true; } } if here { // Emerge on the paired transporter's far side — the cell just past it // along our scan (cx + d), out its back. let tx = cx + dx; let ty = cy + dy; if Board.passable(tx, ty) { transport(ex, ey, tx, ty); return; } } } }