lingdev
↓ 0 downloadsLingDev: a Godot / Unity-style game editor written in Ling. 2-D and 3-D scenes, Flash-style timeline and cutscenes, HUDs over 3-D, Ling scripting, audio / music / MIDI, builds for Windows, Linux, macOS, web, Raspberry Pi and LingOS. Ships with Ling Agent (an N64-style spy FPS), Ling Breaker (a 3-D breakout) and Moko's Picture Book (a kawaii storybook platformer).
Keywords game-engine, editor, 3d, 2d, cutscene, timeline, ling, lingos, raspberry-pi
lingfu install lingdevCode / Files (136)
ling/demo_agent.ling
# demo_agent.ling - builds the "Ling Agent" demo that ships with LingDev: a small first-person spy
# shooter in the spirit of the N64 classics. Title, dossier mission select, difficulty, three stages
# (Reservoir, Facility, Bunker), mission complete / failed. Every stage is ordinary scene nodes, so
# the whole demo opens and edits in LingDev like any project.
fn ag_root() { return p_join(st_get("app_root", "."), "projects/LingAgent") }
fn demo_agent_open() {
if file_exists(p_join(ag_root(), "lingdev.toml")) { return project_open_path(p_join(ag_root(), "lingdev.toml")) }
return demo_agent_build()
}
fn demo_agent_build() {
bind dir = p_join(st_get("app_root", "."), "projects")
plat_mkdir(dir)
if !proj_create(dir, "LingAgent", 0.0, 1280.0, 720.0) { return false }
plat_delete(p_join(proj_root(), "scenes/level.lscene"))
proj_remove_scene("scenes/level.lscene")
st_set("proj_title", "Ling Agent")
st_set("proj_author", "LingDev demo")
ag_music_install()
ag_stage1()
ag_stage2()
ag_stage3()
ag_menus()
st_set("proj_start", "scenes/title.lscene")
proj_save()
gen_write_all()
assets_mark_stale()
open_scene(p_join(proj_root(), "scenes/stage1.lscene"))
status("Built the Ling Agent demo: 3 stages, title, mission select. F5 plays from the title.")
return true
}
# ── music: CC0 tracks from OpenGameArt that ship in LingDev's assets/music/agent ──
fn ag_music_files() { return ["spy_running_theme", "the_quiet_spy", "covert_action", "sneaking_into_the_robot_factory", "infiltration", "dodging_lights", "hacking_the_computer"] }
fn ag_music_install() {
bind src = p_join(st_get("app_root", "."), "assets/music/agent")
bind dst = p_join(proj_root(), "assets/music")
plat_mkdir(p_join(proj_root(), "assets"))
plat_mkdir(dst)
bind names = ag_music_files()
bind i = 0.0
while i < len(names) {
bind f = list_get(names, i) + ".wav"
if file_exists(p_join(src, f)) && !file_exists(p_join(dst, f)) { plat_copy(p_join(src, f), p_join(dst, f)) }
bind i = i + 1.0
}
if file_exists(p_join(src, "CREDITS.txt")) { plat_copy(p_join(src, "CREDITS.txt"), p_join(dst, "CREDITS.txt")) }
return 0.0
}
fn ag_music(stem) { return "assets/music/" + stem + ".wav" }
fn ag_menu_music(name) {
if name == "Title" { return ag_music("spy_running_theme") }
if name == "Mission complete" { return ag_music("dodging_lights") }
if name == "Mission failed" { return ag_music("hacking_the_computer") }
return ag_music("the_quiet_spy")
}
# ── building blocks (floor at y = 0, +y is down) ─────────────────────────────
fn ag_box(name, x, y, z, sx, sy, sz, col, tag) {
bind i = nd_add("box", name, 0.0)
nd_set(i, "px", x)
nd_set(i, "py", y)
nd_set(i, "pz", z)
nd_set(i, "sx", sx)
nd_set(i, "sy", sy)
nd_set(i, "sz", sz)
nd_set(i, "col", col)
nd_set(i, "tag", tag)
return i
}
# a block standing on the floor, h tall
fn ag_block(name, x, z, sx, sz, h, col) { return ag_box(name, x, 0.0 - h * 0.5, z, sx, h * 0.5, sz, col, "solid") }
# an axis-aligned wall from (x0,z0) to (x1,z1), cut into pieces no longer than 8 units
fn ag_wall(x0, z0, x1, z1, h, col) {
bind alongx = abs(x1 - x0) >= abs(z1 - z0)
bind ln = sel(alongx, abs(x1 - x0), abs(z1 - z0))
bind n = max(1.0, ceil(ln / 8.0))
bind k = 0.0
while k < n {
bind a = k / n
bind b = (k + 1.0) / n
bind cx = x0 + (x1 - x0) * (a + b) * 0.5
bind cz = z0 + (z1 - z0) * (a + b) * 0.5
bind half = ln / n * 0.5
if alongx { ag_box("Wall", cx, 0.0 - h * 0.5, cz, half, h * 0.5, 0.15, col, "solid") }
else { ag_box("Wall", cx, 0.0 - h * 0.5, cz, 0.15, h * 0.5, half, col, "solid") }
bind k = k + 1.0
}
}
# a wall with a sliding door at fraction t along it (2 units wide, 2.2 tall, lintel above)
fn ag_wall_door(x0, z0, x1, z1, t, h, col, lock) {
bind alongx = abs(x1 - x0) >= abs(z1 - z0)
bind dx = x0 + (x1 - x0) * t
bind dz = z0 + (z1 - z0) * t
if alongx {
ag_wall(x0, z0, dx - 1.0, z1, h, col)
ag_wall(dx + 1.0, z0, x1, z1, h, col)
} else {
ag_wall(x0, z0, x1, dz - 1.0, h, col)
ag_wall(x0, dz + 1.0, x1, z1, h, col)
}
bind lh = (h - 2.2) * 0.5
ag_box("Lintel", dx, 0.0 - 2.2 - lh, dz, sel(alongx, 1.0, 0.15), lh, sel(alongx, 0.15, 1.0), col, "solid")
bind d = ag_box(sel(lock == "", "Door", "Locked door"), dx, 0.0 - 1.1, dz, sel(alongx, 1.0, 0.08), 1.1, sel(alongx, 0.08, 1.0), sel(lock == "", c_rgb(80.0, 98.0, 130.0), c_rgb(140.0, 60.0, 50.0)), "door")
if lock != "" { nd_set(d, "ex", "lock=" + lock) }
return d
}
fn ag_room(x0, z0, x1, z1, h, col) {
ag_wall(x0, z0, x1, z0, h, col)
ag_wall(x0, z1, x1, z1, h, col)
ag_wall(x0, z0, x0, z1, h, col)
ag_wall(x1, z0, x1, z1, h, col)
}
fn ag_guard(name, x, z, yaw, weapon, look, patrol) {
bind i = nd_add("actor", name, 0.0)
nd_set(i, "px", x)
nd_set(i, "py", 0.0)
nd_set(i, "pz", z)
nd_set(i, "ry", yaw)
nd_set(i, "sx", 0.3)
nd_set(i, "sy", 0.9)
nd_set(i, "sz", 0.3)
nd_set(i, "tag", "guard")
nd_set(i, "c", 0.0)
nd_set(i, "col", list_get(gd_colors(look), 0))
nd_set(i, "ex", "weapon=" + num_str(weapon) + ";look=" + look + sel(patrol == "", "", ";patrol=" + patrol))
return i
}
fn ag_pick(item, x, z, amount) {
bind i = ag_box("Pickup " + item, x, 0.0 - 0.15, z, 0.15, 0.15, 0.15, c_rgb(240.0, 200.0, 60.0), "pickup")
nd_set(i, "ex", "item=" + item + sel(amount > 0.0, ";amount=" + num_str(amount), ""))
return i
}
fn ag_console(name, x, z, yaw) {
bind c = ag_box(name, x, 0.0 - 0.6, z, 0.45, 0.6, 0.3, c_rgb(92.0, 98.0, 106.0), "console")
nd_set(c, "ry", yaw)
nd_set(c, "ex", "hp=60")
ag_box(name + " screen", x, 0.0 - 1.22, z, 0.4, 0.04, 0.26, c_rgb(60.0, 220.0, 120.0), "deco")
return c
}
fn ag_exit(x, z) {
ag_box("Extraction pad", x, 0.0 - 0.02, z, 1.4, 0.02, 1.4, c_rgb(60.0, 200.0, 90.0), "deco")
bind t = nd_add("trigger", "Extraction", 0.0)
nd_set(t, "px", x)
nd_set(t, "py", 0.0 - 1.0)
nd_set(t, "pz", z)
nd_set(t, "sx", 1.4)
nd_set(t, "sy", 1.5)
nd_set(t, "sz", 1.4)
nd_set(t, "a", 0.0)
nd_set(t, "txt", "exit")
return t
}
fn ag_light(x, y, z, col, intensity, radius) {
bind l = nd_add("light", "Light", 0.0)
nd_set(l, "px", x)
nd_set(l, "py", y)
nd_set(l, "pz", z)
nd_set(l, "col", col)
nd_set(l, "a", intensity)
nd_set(l, "b", radius)
return l
}
# the floor as 10-unit slabs in two shades: lights stay local and it reads like a tiled texture
fn ag_floor(fsx, fsz, col) {
bind nx = ceil(fsx * 2.0 / 10.0)
bind nz = ceil(fsz * 2.0 / 10.0)
bind hx = fsx / nx
bind hz = fsz / nz
bind ix = 0.0
while ix < nx {
bind iz = 0.0
while iz < nz {
bind c = sel((ix + iz) % 2.0 < 0.5, col, c_tint(col, 0.9))
ag_box("Floor", 0.0 - fsx + hx * (ix * 2.0 + 1.0), 0.02, 0.0 - fsz + hz * (iz * 2.0 + 1.0), hx, 0.02, hz, c, "deco")
bind iz = iz + 1.0
}
bind ix = ix + 1.0
}
}
# a fresh stage: floor, player, spawn; returns nothing
fn ag_stage_begin(name, n, obj, bg, fog, floor_col, fsx, fsz, sx, sz, yaw) {
sc_new(name, "3d")
st_lclear("undo")
st_lclear("redo")
sel_clear()
sc_set("bg", bg)
sc_set("amb", 0.5)
sc_set("floor", 0.0)
sc_set("grav", 12.0)
sc_set("music", ag_music(list_get(["covert_action", "sneaking_into_the_robot_factory", "infiltration"], clampf(n - 1.0, 0.0, 2.0))))
sc_set("ex", "mode=fps;stage=" + num_str(n) + ";obj=" + obj + ";fog=" + fog + ";shade=0;rim=0")
ag_floor(fsx, fsz, floor_col)
bind sp = nd_add("spawn", "Spawn", 0.0)
nd_set(sp, "px", sx)
nd_set(sp, "pz", sz)
nd_set(sp, "ry", yaw)
bind p = nd_add("actor", "Agent", 0.0)
nd_set(p, "px", sx)
nd_set(p, "py", 0.0 - 0.9)
nd_set(p, "pz", sz)
nd_set(p, "ry", yaw)
nd_set(p, "sx", 0.32)
nd_set(p, "sy", 0.9)
nd_set(p, "sz", 0.32)
nd_set(p, "d", 6.2)
nd_set(p, "tag", "player")
nd_set(p, "c", 1.0)
nd_set(p, "col", c_rgb(40.0, 44.0, 60.0))
st_set("ed_tx", sx)
st_set("ed_ty", 0.0 - 1.0)
st_set("ed_tz", sz)
st_set("ed_yaw", yaw)
st_set("ed_pitch", 0.6)
st_set("ed_dist", 40.0)
}
# ── stage 1: Reservoir — outdoors under the dam at dusk ──────────────────────
fn ag_stage1() {
bind concrete = c_rgb(150.0, 148.0, 138.0)
bind dark = c_rgb(96.0, 96.0, 90.0)
ag_stage_begin("Reservoir", 1.0, "consoles_2,kill_5,exit", c_rgb(70.0, 78.0, 110.0), "70,78,110,25,95", c_rgb(84.0, 92.0, 70.0), 42.0, 32.0, 0.0 - 32.0, 0.0 - 25.0, 0.7)
# the dam: a tall wall along the north edge with a walkway lip, fences elsewhere
ag_wall(0.0 - 42.0, 32.0, 42.0, 32.0, 14.0, concrete)
bind k = 0.0
while k < 8.0 {
ag_block("Dam buttress", 0.0 - 35.0 + k * 10.0, 30.6, 1.2, 1.2, 14.0, dark)
bind k = k + 1.0
}
ag_wall(0.0 - 42.0, 0.0 - 32.0, 42.0, 0.0 - 32.0, 3.0, c_rgb(70.0, 80.0, 60.0))
ag_wall(0.0 - 42.0, 0.0 - 32.0, 0.0 - 42.0, 32.0, 3.0, c_rgb(70.0, 80.0, 60.0))
ag_wall(42.0, 0.0 - 32.0, 42.0, 32.0, 3.0, c_rgb(70.0, 80.0, 60.0))
# guard towers with an alarm console at the foot of each
bind towers = [0.0 - 16.0, 6.0, 18.0, 12.0]
bind t = 0.0
while t < 2.0 {
bind tx = list_get(towers, t * 2.0)
bind tz = list_get(towers, t * 2.0 + 1.0)
ag_block("Tower leg", tx - 1.4, tz - 1.4, 0.15, 0.15, 5.0, dark)
ag_block("Tower leg", tx + 1.4, tz - 1.4, 0.15, 0.15, 5.0, dark)
ag_block("Tower leg", tx - 1.4, tz + 1.4, 0.15, 0.15, 5.0, dark)
ag_block("Tower leg", tx + 1.4, tz + 1.4, 0.15, 0.15, 5.0, dark)
ag_box("Tower platform", tx, 0.0 - 5.1, tz, 1.8, 0.12, 1.8, c_rgb(110.0, 90.0, 60.0), "solid")
ag_box("Tower roof", tx, 0.0 - 6.8, tz, 2.0, 0.1, 2.0, c_rgb(70.0, 60.0, 50.0), "solid")
ag_console("Alarm console " + num_str(t + 1.0), tx, tz - 2.6, 0.0)
ag_light(tx, 0.0 - 6.0, tz, c_rgb(255.0, 220.0, 160.0), 1.3, 14.0)
bind t = t + 1.0
}
# jersey barriers and crate stacks: cover
bind bars = [0.0 - 24.0, 0.0 - 12.0, 0.0 - 6.0, 0.0 - 18.0, 4.0, 0.0 - 4.0, 26.0, 0.0 - 8.0, 0.0 - 4.0, 18.0, 30.0, 20.0, 10.0, 24.0]
bind b = 0.0
while b < len(bars) / 2.0 {
ag_block("Barrier", list_get(bars, b * 2.0), list_get(bars, b * 2.0 + 1.0), 1.5, 0.35, 1.05, c_rgb(170.0, 168.0, 160.0))
bind b = b + 1.0
}
bind crates = [0.0 - 28.0, 4.0, 0.0 - 10.0, 14.0, 8.0, 0.0 - 22.0, 30.0, 0.0 - 20.0, 2.0, 20.0]
bind c = 0.0
while c < len(crates) / 2.0 {
bind cx = list_get(crates, c * 2.0)
bind cz = list_get(crates, c * 2.0 + 1.0)
ag_block("Crate", cx, cz, 0.7, 0.7, 1.4, c_rgb(130.0, 100.0, 60.0))
ag_block("Crate", cx + 1.45, cz, 0.7, 0.7, 1.4, c_rgb(120.0, 92.0, 56.0))
ag_box("Crate", cx + 0.7, 0.0 - 2.1, cz, 0.7, 0.7, 0.7, c_rgb(136.0, 106.0, 64.0), "solid")
bind c = c + 1.0
}
# the supply shed
ag_wall(4.0, 0.0 - 22.0, 16.0, 0.0 - 22.0, 3.0, c_rgb(110.0, 120.0, 100.0))
ag_wall(4.0, 0.0 - 14.0, 16.0, 0.0 - 14.0, 3.0, c_rgb(110.0, 120.0, 100.0))
ag_wall(16.0, 0.0 - 22.0, 16.0, 0.0 - 14.0, 3.0, c_rgb(110.0, 120.0, 100.0))
ag_wall_door(4.0, 0.0 - 22.0, 4.0, 0.0 - 14.0, 0.5, 3.0, c_rgb(110.0, 120.0, 100.0), "")
ag_box("Shed roof", 10.0, 0.0 - 3.1, 0.0 - 18.0, 6.2, 0.1, 4.2, c_rgb(80.0, 70.0, 60.0), "solid")
ag_light(10.0, 0.0 - 2.6, 0.0 - 18.0, c_rgb(255.0, 240.0, 200.0), 1.8, 8.0)
ag_pick("armor", 13.0, 0.0 - 20.0, 100.0)
ag_pick("smg", 13.0, 0.0 - 16.0, 60.0)
ag_pick("ammo", 0.0 - 26.0, 0.0 - 20.0, 0.0)
ag_pick("rifle", 22.0, 16.0, 60.0)
ag_pick("ammo", 0.0 - 12.0, 20.0, 0.0)
# guards
ag_guard("Sentry", 0.0 - 20.0, 0.0 - 10.0, 3.1, 1.0, "soldier", "-20_-10_-20_8")
ag_guard("Sentry", 0.0 - 14.0, 2.0, 1.5, 2.0, "soldier", "")
ag_guard("Patrol", 0.0, 0.0, 0.0, 2.0, "soldier", "0_0_12_0_12_-8_0_-8")
ag_guard("Shed guard", 10.0, 0.0 - 11.0, 3.1, 1.0, "guard", "")
ag_guard("Tower guard", 20.0, 9.0, 3.1, 3.0, "soldier", "")
ag_guard("Dam patrol", 24.0, 26.0, 1.5, 2.0, "soldier", "8_26_32_26")
ag_guard("Officer", 32.0, 18.0, 3.6, 4.0, "officer", "")
ag_exit(34.0, 27.0)
ag_light(0.0, 0.0 - 30.0, 0.0, c_rgb(170.0, 170.0, 220.0), 2.5, 120.0)
ag_light(30.0, 0.0 - 4.0, 26.0, c_rgb(120.0, 255.0, 140.0), 1.6, 10.0)
# the view the title screen orbits
st_set("ed_tx", 0.0) st_set("ed_ty", 0.0 - 3.0) st_set("ed_tz", 6.0) st_set("ed_pitch", 0.16) st_set("ed_dist", 30.0)
ag_light(0.0 - 30.0, 0.0 - 4.0, 0.0 - 24.0, c_rgb(255.0, 210.0, 150.0), 1.0, 12.0)
save_scene(p_join(proj_root(), "scenes/stage1.lscene"))
}
# ── stage 2: Facility — labs off a long corridor ─────────────────────────────
fn ag_stage2() {
bind tile = c_rgb(168.0, 174.0, 180.0)
bind trim = c_rgb(150.0, 160.0, 170.0)
ag_stage_begin("Facility", 2.0, "item_keycard,consoles_1,kill_6,exit", c_rgb(14.0, 16.0, 20.0), "20,24,30,18,60", c_rgb(118.0, 124.0, 130.0), 21.0, 31.0, 0.0, 0.0 - 27.0, 0.0)
sc_set("amb", 0.42)
ag_room(0.0 - 20.0, 0.0 - 30.0, 20.0, 30.0, 3.4, tile)
# corridor walls x = -2 and x = 2, a door into each of the six labs
bind locks = ["", "", "", "keycard", "", ""]
bind side = 0.0
while side < 2.0 {
bind wx = sel(side < 0.5, 0.0 - 2.0, 2.0)
bind seg = 0.0
while seg < 3.0 {
bind z0 = 0.0 - 30.0 + seg * 20.0
ag_wall_door(wx, z0, wx, z0 + 20.0, 0.5, 3.4, trim, list_get(locks, side * 3.0 + seg))
bind seg = seg + 1.0
}
# walls between the labs
bind ox = sel(side < 0.5, 0.0 - 20.0, 2.0)
ag_wall(ox, 0.0 - 10.0, ox + 18.0, 0.0 - 10.0, 3.4, tile)
ag_wall(ox, 10.0, ox + 18.0, 10.0, 3.4, tile)
bind side = side + 1.0
}
ag_box("Ceiling", 0.0, 0.0 - 3.5, 0.0, 20.2, 0.1, 30.2, c_rgb(170.0, 172.0, 176.0), "solid")
# labs: benches, cabinets
bind benches = [0.0 - 12.0, 0.0 - 20.0, 0.0 - 12.0, 0.0, 0.0 - 12.0, 20.0, 11.0, 0.0 - 20.0, 11.0, 0.0, 11.0, 20.0]
bind b = 0.0
while b < 6.0 {
bind bx = list_get(benches, b * 2.0)
bind bz = list_get(benches, b * 2.0 + 1.0)
ag_block("Lab bench", bx, bz - 3.0, 3.0, 0.6, 0.95, c_rgb(190.0, 192.0, 196.0))
ag_block("Lab bench", bx, bz + 3.5, 3.0, 0.6, 0.95, c_rgb(190.0, 192.0, 196.0))
ag_block("Cabinet", bx + sel(bx < 0.0, 0.0 - 6.5, 7.0), bz, 0.4, 1.2, 2.0, c_rgb(90.0, 110.0, 130.0))
ag_light(bx, 0.0 - 3.2, bz, c_rgb(235.0, 245.0, 255.0), 1.2, 10.0)
bind b = b + 1.0
}
bind k = 0.0
while k < 4.0 {
ag_light(0.0, 0.0 - 3.2, 0.0 - 24.0 + k * 16.0, c_rgb(220.0, 235.0, 255.0), 1.0, 9.0)
bind k = k + 1.0
}
# the keycard in the north-west lab, the mainframe behind the locked door (south-east lab)
ag_pick("keycard", 0.0 - 15.0, 22.0, 0.0)
ag_console("Mainframe", 14.0, 0.0 - 26.0, 0.0)
ag_pick("shotgun", 0.0 - 16.0, 0.0 - 24.0, 15.0)
ag_pick("armor", 15.0, 24.0, 100.0)
ag_pick("ammo", 0.0 - 16.0, 4.0, 0.0)
ag_pick("ammo", 16.0, 4.0, 0.0)
ag_guard("Lab guard", 0.0 - 12.0, 0.0 - 16.0, 1.5, 1.0, "guard", "")
ag_guard("Scientist", 0.0 - 10.0, 2.0, 2.4, 1.0, "scientist", "-10_2_-14_-4")
ag_guard("Corridor patrol", 0.0, 6.0, 3.1, 2.0, "guard", "0_6_0_26_0_-18")
ag_guard("Lab guard", 0.0 - 14.0, 18.0, 0.7, 2.0, "guard", "")
ag_guard("Scientist", 12.0, 2.0, 4.0, 1.0, "scientist", "")
ag_guard("Lab guard", 12.0, 18.0, 4.3, 2.0, "guard", "12_18_16_14")
ag_guard("Security chief", 14.0, 0.0 - 20.0, 3.1, 4.0, "officer", "")
ag_guard("Lab guard", 8.0, 0.0 - 16.0, 4.6, 2.0, "guard", "")
ag_exit(0.0, 27.5)
save_scene(p_join(proj_root(), "scenes/stage2.lscene"))
}
# ── stage 3: Bunker — a dim concrete maze ────────────────────────────────────
fn ag_stage3() {
bind wallc = c_rgb(88.0, 96.0, 84.0)
ag_stage_begin("Bunker", 3.0, "item_disk,kill_all,exit", c_rgb(6.0, 6.0, 8.0), "8,10,8,10,40", c_rgb(60.0, 64.0, 58.0), 26.0, 26.0, 0.0 - 22.0, 0.0 - 22.0, 0.8)
sc_set("amb", 0.32)
ag_room(0.0 - 25.0, 0.0 - 25.0, 25.0, 25.0, 3.0, wallc)
# the maze: [x0, z0, x1, z1] segments
bind segs = [0.0 - 25.0, 0.0 - 12.0, 0.0 - 10.0, 0.0 - 12.0, 0.0 - 4.0, 0.0 - 25.0, 0.0 - 4.0, 0.0 - 14.0, 0.0 - 4.0, 0.0 - 8.0, 0.0 - 4.0, 6.0, 0.0 - 18.0, 0.0, 0.0 - 4.0, 0.0, 0.0 - 18.0, 0.0, 0.0 - 18.0, 14.0, 0.0 - 25.0, 18.0, 0.0 - 10.0, 18.0, 0.0 - 10.0, 10.0, 0.0 - 10.0, 25.0, 4.0, 0.0 - 18.0, 25.0, 0.0 - 18.0, 4.0, 0.0 - 18.0, 4.0, 0.0 - 6.0, 10.0, 0.0 - 10.0, 10.0, 8.0, 10.0, 8.0, 20.0, 8.0, 2.0, 14.0, 2.0, 25.0, 2.0, 14.0, 6.0, 14.0, 16.0, 14.0, 16.0, 25.0, 18.0, 0.0 - 10.0, 18.0, 2.0]
bind s = 0.0
while s < len(segs) / 4.0 {
ag_wall(list_get(segs, s * 4.0), list_get(segs, s * 4.0 + 1.0), list_get(segs, s * 4.0 + 2.0), list_get(segs, s * 4.0 + 3.0), 3.0, wallc)
bind s = s + 1.0
}
ag_wall_door(10.0, 14.0, 16.0, 14.0, 0.5, 3.0, wallc, "")
ag_box("Ceiling", 0.0, 0.0 - 3.1, 0.0, 25.2, 0.1, 25.2, c_rgb(40.0, 42.0, 38.0), "solid")
# oil drums and crates
bind drums = [0.0 - 20.0, 0.0 - 18.0, 0.0 - 8.0, 0.0 - 20.0, 0.0 - 14.0, 8.0, 14.0, 0.0 - 22.0, 22.0, 0.0 - 4.0, 6.0, 4.0, 0.0 - 22.0, 22.0, 12.0, 20.0]
bind d = 0.0
while d < len(drums) / 2.0 {
ag_block("Drum", list_get(drums, d * 2.0), list_get(drums, d * 2.0 + 1.0), 0.45, 0.45, 1.2, c_rgb(70.0, 90.0, 60.0))
bind d = d + 1.0
}
# red emergency lights down the halls
bind lights = [0.0 - 20.0, 0.0 - 20.0, 0.0 - 10.0, 0.0 - 6.0, 0.0 - 20.0, 8.0, 0.0 - 16.0, 22.0, 0.0, 0.0 - 20.0, 0.0, 10.0, 14.0, 0.0 - 12.0, 20.0, 0.0 - 22.0, 14.0, 3.0, 8.0, 20.0, 20.0, 20.0]
bind l = 0.0
while l < len(lights) / 2.0 {
ag_light(list_get(lights, l * 2.0), 0.0 - 2.8, list_get(lights, l * 2.0 + 1.0), sel(l % 3.0 < 0.5, c_rgb(255.0, 60.0, 40.0), c_rgb(255.0, 220.0, 160.0)), 1.8, 11.0)
bind l = l + 1.0
}
ag_pick("disk", 13.0, 20.0, 0.0)
ag_pick("rifle", 0.0 - 22.0, 22.0, 60.0)
ag_pick("armor", 22.0, 0.0 - 22.0, 100.0)
ag_pick("armor", 0.0 - 7.0, 3.0, 50.0)
ag_pick("ammo", 14.0, 0.0 - 14.0, 0.0)
ag_pick("shotgun", 0.0 - 22.0, 0.0 - 4.0, 15.0)
ag_guard("Bunker guard", 0.0 - 8.0, 0.0 - 18.0, 4.7, 1.0, "soldier", "-8_-18_-8_-10")
ag_guard("Bunker guard", 0.0 - 12.0, 6.0, 1.5, 2.0, "soldier", "")
ag_guard("Bunker guard", 0.0 - 20.0, 12.0, 0.0, 2.0, "soldier", "-20_12_-20_4_-12_4")
ag_guard("Bunker guard", 0.0 - 6.0, 22.0, 1.5, 3.0, "soldier", "")
ag_guard("Bunker guard", 7.0, 0.0 - 8.0, 0.0, 2.0, "soldier", "7_-8_7_10")
ag_guard("Bunker guard", 14.0, 0.0 - 4.0, 3.1, 4.0, "soldier", "")
ag_guard("Bunker guard", 22.0, 10.0, 4.7, 2.0, "soldier", "22_10_22_-6")
ag_guard("Commandant", 12.0, 21.0, 3.1, 3.0, "officer", "")
ag_guard("Bunker guard", 20.0, 20.0, 3.9, 4.0, "soldier", "")
ag_exit(22.0, 0.0 - 22.0)
save_scene(p_join(proj_root(), "scenes/stage3.lscene"))
}
# ── menus: title, dossier mission select, difficulty, complete, failed ──────
fn ag_menu_begin(name) {
sc_new(name, "2d")
st_lclear("undo")
st_lclear("redo")
sel_clear()
sc_set("bg", c_rgb(16.0, 18.0, 14.0))
sc_set("music", ag_menu_music(name))
}
fn ag_label(txt, x, y, w, size, col, anc) {
bind l = nd_add("label", "Text", 0.0)
nd_set(l, "txt", txt)
nd_set(l, "px", x)
nd_set(l, "py", y)
nd_set(l, "w", w)
nd_set(l, "h", size + 8.0)
nd_set(l, "a", size)
nd_set(l, "col", col)
nd_set(l, "anc", anc)
nd_set(l, "name", str_slice(txt, 0.0, min(18.0, str_len(txt))))
return l
}
fn ag_button(txt, act, x, y, w, col) {
bind b = nd_add("button", txt, 0.0)
nd_set(b, "txt", txt)
nd_set(b, "scr", act)
nd_set(b, "px", x)
nd_set(b, "py", y)
nd_set(b, "w", w)
nd_set(b, "h", 38.0)
nd_set(b, "a", 16.0)
nd_set(b, "col", col)
return b
}
# the manila folder every menu sits in
fn ag_folder(title) {
bind f = nd_add("panel", "Folder", 0.0)
nd_set(f, "px", 140.0)
nd_set(f, "py", 70.0)
nd_set(f, "w", 1000.0)
nd_set(f, "h", 590.0)
nd_set(f, "a", 1.0)
nd_set(f, "col", c_rgb(196.0, 172.0, 122.0))
bind tab = nd_add("panel", "Folder tab", 0.0)
nd_set(tab, "px", 160.0)
nd_set(tab, "py", 40.0)
nd_set(tab, "w", 260.0)
nd_set(tab, "h", 36.0)
nd_set(tab, "a", 1.0)
nd_set(tab, "col", c_rgb(186.0, 162.0, 112.0))
ag_label(title, 176.0, 46.0, 240.0, 18.0, c_rgb(60.0, 40.0, 20.0), "tl")
bind st = ag_label("TOP SECRET", 0.0 - 170.0, 96.0, 220.0, 22.0, c_rgb(170.0, 30.0, 30.0), "tr")
return f
}
fn ag_menus() {
bind ink = c_rgb(54.0, 36.0, 18.0)
bind faint = c_rgb(110.0, 86.0, 52.0)
# title over the reservoir
ag_menu_begin("Title")
sc_set("ex", "bg3d=scenes/stage1.lscene;bgspin=0.04;bgzd=2")
bind band = nd_add("panel", "Title band", 0.0)
nd_set(band, "px", 0.0) nd_set(band, "py", 130.0) nd_set(band, "w", 1280.0) nd_set(band, "h", 150.0) nd_set(band, "a", 0.62) nd_set(band, "col", c_rgb(8.0, 10.0, 8.0)) nd_set(band, "anc", "tc")
bind band2 = nd_add("panel", "Hint band", 0.0)
nd_set(band2, "px", 0.0) nd_set(band2, "py", 0.0 - 50.0) nd_set(band2, "w", 1280.0) nd_set(band2, "h", 130.0) nd_set(band2, "a", 0.62) nd_set(band2, "col", c_rgb(8.0, 10.0, 8.0)) nd_set(band2, "anc", "bc")
ag_label("LING AGENT", 0.0, 150.0, 1280.0, 72.0, c_rgb(240.0, 230.0, 200.0), "tc")
ag_label("a LingDev demo in the spirit of the N64 spy shooters", 0.0, 240.0, 1280.0, 18.0, c_rgb(200.0, 200.0, 180.0), "tc")
bind ps = ag_button("PRESS START", "goto:scenes/select.lscene", 0.0, 0.0 - 150.0, 300.0, c_rgb(230.0, 220.0, 190.0))
nd_set(ps, "anc", "bc")
nd_set(ps, "ex", "style=text;blink=1")
nd_set(ps, "a", 22.0)
ag_label("mouse look WASD move click fire R reload Q E / wheel / 1-5 weapons P pause", 0.0, 0.0 - 70.0, 1280.0, 14.0, c_rgb(160.0, 160.0, 150.0), "bc")
save_scene(p_join(proj_root(), "scenes/title.lscene"))
# mission select
ag_menu_begin("Mission select")
ag_folder("MISSION SELECT")
ag_label("Choose a mission, agent.", 180.0, 110.0, 600.0, 16.0, faint, "tl")
bind names = ["1. Reservoir", "2. Facility", "3. Bunker"]
bind briefs = ["Infiltrate the reservoir at dusk. Disable the alarm consoles under the guard towers and reach the extraction pad below the dam.", "A research facility. Find the security keycard, destroy the mainframe behind the locked lab door, and get out at the north end.", "A concrete bunker under the hill. Recover the data disk, leave no hostile standing, and reach the extraction point."]
bind k = 0.0
while k < 3.0 {
bind y = 170.0 + k * 140.0
bind nm = list_get(names, k)
bind act = "store:stage=scenes/stage" + num_str(k + 1.0) + ".lscene;store:stagename=" + nm + ";store:brief=" + list_get(briefs, k) + ";goto:scenes/difficulty.lscene"
ag_button(nm, act, 180.0, y, 300.0, c_rgb(60.0, 50.0, 30.0))
ag_label(list_get(briefs, k), 500.0, y + 2.0, 600.0, 14.0, ink, "tl")
ag_label("{$done" + num_str(k + 1.0) + "}", 500.0, y + 58.0, 600.0, 14.0, c_rgb(30.0, 110.0, 40.0), "tl")
bind k = k + 1.0
}
ag_button("Back", "goto:scenes/title.lscene", 180.0, 600.0, 140.0, c_rgb(60.0, 50.0, 30.0))
save_scene(p_join(proj_root(), "scenes/select.lscene"))
# difficulty
ag_menu_begin("Difficulty")
ag_folder("SELECT DIFFICULTY")
ag_label("{$stagename}", 180.0, 110.0, 800.0, 30.0, ink, "tl")
ag_label("{$brief}", 180.0, 160.0, 900.0, 15.0, faint, "tl")
bind diffs = ["Agent", "Special Agent", "Elite Agent"]
bind notes = ["Fewer objectives. Guards are slow and miss often.", "Every objective. Guards fight back.", "Every objective. Tougher, faster, deadlier guards."]
bind k = 0.0
while k < 3.0 {
bind y = 250.0 + k * 90.0
ag_button(list_get(diffs, k), "set:difficulty=" + num_str(k) + ";goto:@stage", 180.0, y, 300.0, c_rgb(60.0, 50.0, 30.0))
ag_label(list_get(notes, k), 500.0, y + 10.0, 600.0, 14.0, ink, "tl")
bind k = k + 1.0
}
ag_button("Back", "goto:scenes/select.lscene", 180.0, 600.0, 140.0, c_rgb(60.0, 50.0, 30.0))
save_scene(p_join(proj_root(), "scenes/difficulty.lscene"))
# mission complete
ag_menu_begin("Mission complete")
ag_folder("MISSION STATUS")
ag_label("MISSION COMPLETE", 180.0, 120.0, 900.0, 40.0, c_rgb(30.0, 100.0, 40.0), "tl")
ag_label("{$stagename} / {$diff}", 180.0, 180.0, 900.0, 18.0, ink, "tl")
ag_label("Time {$time}", 180.0, 250.0, 500.0, 20.0, ink, "tl")
ag_label("Accuracy {$acc} ({$shots} shots)", 180.0, 290.0, 600.0, 20.0, ink, "tl")
ag_label("Kills {$kills} Headshots {$heads}", 180.0, 330.0, 600.0, 20.0, ink, "tl")
ag_button("Continue", "goto:scenes/select.lscene", 180.0, 560.0, 220.0, c_rgb(60.0, 50.0, 30.0))
save_scene(p_join(proj_root(), "scenes/complete.lscene"))
# mission failed
ag_menu_begin("Mission failed")
ag_folder("MISSION STATUS")
ag_label("MISSION FAILED", 180.0, 120.0, 900.0, 40.0, c_rgb(160.0, 30.0, 30.0), "tl")
ag_label("{$stagename} / {$diff}", 180.0, 180.0, 900.0, 18.0, ink, "tl")
ag_label("{$fail}", 180.0, 240.0, 900.0, 20.0, ink, "tl")
ag_button("Retry", "goto:@stage", 180.0, 500.0, 220.0, c_rgb(60.0, 50.0, 30.0))
ag_button("Mission select", "goto:scenes/select.lscene", 180.0, 560.0, 220.0, c_rgb(60.0, 50.0, 30.0))
save_scene(p_join(proj_root(), "scenes/failed.lscene"))
}