Make RustRaptor the repository root and track its playable bundle
No files matched your search
@@ -0,0 +1,33 @@
|
||||
# Iron Coast: Relay Warden
|
||||
|
||||
Standalone API 1 boss package used by the Tidal Relay mission. It has no dependencies and never decides when a mission finishes. Enable the mission to select this package automatically through its dependency.
|
||||
|
||||
- Enemy: `iron_coast_boss_2:unit`, health 160, reward 1,800 credits, airborne boss.
|
||||
- Projectile: `iron_coast_boss_2:projectile`, damage 3, speed 4, 8×8 logical canvas.
|
||||
- Completion observation: `iron_coast_boss_2:defeated` increases once for each actual death. Duplicate notifications are harmless; despawning or time passing does not count.
|
||||
- Art: one complete static frame on a 96×64 logical canvas. SD and HD preserve the same origin, full silhouette and true alpha. HD is 768×512; gameplay collision dimensions remain 96×64.
|
||||
- Audio: `shot`, `warning` and `death` namespaced sound IDs, each a generated, non-silent 22,050 Hz, mono, 16-bit PCM WAV.
|
||||
|
||||
The Warden enters at two logical pixels per tick, stops at y=24, and patrols x=80…144. Each entity tracks its own arrival timer and direction. An audible radar charge starts 12 ticks before each 54-tick volley. Odd volleys fire two parallel outer lanes; even volleys fire two diverging inner lanes. The opening below its central radar dish remains readable. Each queued spawn registers its life even if a projectile has already killed it before callback dispatch; that early kill still counts when the engine delivers its actual death. Timers and directions are removed when the aircraft dies, and dead units cannot emit movement, warnings or shots. Difficulty scaling comes from the engine, including its existing half-health rule on the two lower difficulties.
|
||||
|
||||
## Asset sources
|
||||
|
||||
The two masters were generated using the built-in imagegen tool from the saved prompts, with the existing generated Iron Coast cruiser inspected for material and color style. The Warden and cyan radar pulse have new silhouettes; no original game art or recordings are copied into this package. `provenance.json`, `art-qa.json` and `audio-recipe.json` record inputs, processing, alpha bounds and hashes. All PNGs, WAVs and prompts are intended for Git.
|
||||
|
||||
Recreate the SD/HD output and audio from the saved masters with:
|
||||
|
||||
```sh
|
||||
python3 RustRaptor/mods/iron_coast_boss_2/build_assets.py
|
||||
```
|
||||
|
||||
## Validation
|
||||
|
||||
The package contains a small Rust validation program that uses the game's public modding and deterministic simulation API. It validates package artwork and WAV decoding, both telegraphs and alternating volleys, separate entity timers, actual death rewards/objectives, repeated and dead callbacks, cleanup, a real lethal projectile during the spawn step, and reuse of an entity ID for a new life. It requires no original game assets.
|
||||
|
||||
```sh
|
||||
CARGO_TARGET_DIR=RustRaptor/target cargo run --offline \
|
||||
--manifest-path RustRaptor/mods/iron_coast_boss_2/validation/Cargo.toml \
|
||||
--bin validate-boss
|
||||
```
|
||||
|
||||
Its saved result is `validation/report.json`. Tidal Relay's validation also exercises this boss in the complete mission with the real original content registry.
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"logical_unit": [
|
||||
96,
|
||||
64
|
||||
],
|
||||
"logical_projectile": [
|
||||
8,
|
||||
8
|
||||
],
|
||||
"hd_scale": 8,
|
||||
"frames": 1,
|
||||
"alpha": "True RGBA; runtime palette mapping uses alpha >= 128",
|
||||
"conversion": "Full source canvas Lanczos HD, BOX SD; no sprite redraw or catalog copies",
|
||||
"files": [
|
||||
{
|
||||
"file": "art/unit-sd.png",
|
||||
"size": [
|
||||
96,
|
||||
64
|
||||
],
|
||||
"opaque_bounds": [
|
||||
3,
|
||||
3,
|
||||
93,
|
||||
62
|
||||
],
|
||||
"source": "art/unit-generated-v1.png",
|
||||
"alpha_min_max": [
|
||||
0,
|
||||
254
|
||||
],
|
||||
"sha256": "0d41d6bf34f7812b684c724aa44c9477187dfbe7a38faa958f0d3887d7e9770d"
|
||||
},
|
||||
{
|
||||
"file": "art/unit-hd.png",
|
||||
"size": [
|
||||
768,
|
||||
512
|
||||
],
|
||||
"opaque_bounds": [
|
||||
20,
|
||||
21,
|
||||
748,
|
||||
498
|
||||
],
|
||||
"source": "art/unit-generated-v1.png",
|
||||
"alpha_min_max": [
|
||||
0,
|
||||
255
|
||||
],
|
||||
"sha256": "272e1a206d40e06ddc54a0c9cb763db9a776647d3392c2a063cc551cfab939cb"
|
||||
},
|
||||
{
|
||||
"file": "art/projectile-sd.png",
|
||||
"size": [
|
||||
8,
|
||||
8
|
||||
],
|
||||
"opaque_bounds": [
|
||||
3,
|
||||
2,
|
||||
5,
|
||||
5
|
||||
],
|
||||
"source": "art/projectile-generated-v1.png",
|
||||
"alpha_min_max": [
|
||||
0,
|
||||
225
|
||||
],
|
||||
"sha256": "f871add4de5a44d6dff953d12369bcff56c2a1f65bdffc861d962b8d1b6616e4"
|
||||
},
|
||||
{
|
||||
"file": "art/projectile-hd.png",
|
||||
"size": [
|
||||
64,
|
||||
64
|
||||
],
|
||||
"opaque_bounds": [
|
||||
24,
|
||||
13,
|
||||
40,
|
||||
49
|
||||
],
|
||||
"source": "art/projectile-generated-v1.png",
|
||||
"alpha_min_max": [
|
||||
0,
|
||||
255
|
||||
],
|
||||
"sha256": "abd22235128301f609feebcc73bc3910ef248b678002cea88cf655efd739ffb6"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 380 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 135 B |
|
After Width: | Height: | Size: 1.9 MiB |
|
After Width: | Height: | Size: 363 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"generator": "build_assets.py deterministic chirp/noise synthesis; no recordings",
|
||||
"files": [
|
||||
{
|
||||
"file": "audio/shot.wav",
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"samples": 4630,
|
||||
"duration": 0.21,
|
||||
"peak": 25558,
|
||||
"rms": 4171.3,
|
||||
"seed": 212,
|
||||
"sha256": "67c6b5b2dd9748584f9db50c3c251cc441218a3600b40f5c96407c2da5928d62"
|
||||
},
|
||||
{
|
||||
"file": "audio/warning.wav",
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"samples": 7938,
|
||||
"duration": 0.36,
|
||||
"peak": 25558,
|
||||
"rms": 8556.4,
|
||||
"seed": 222,
|
||||
"sha256": "915cf9a4e73f7d3a9b89e036640211e8f052bc490637c5244566f3c35f69080e"
|
||||
},
|
||||
{
|
||||
"file": "audio/death.wav",
|
||||
"sample_rate": 22050,
|
||||
"channels": 1,
|
||||
"bits": 16,
|
||||
"samples": 20947,
|
||||
"duration": 0.95,
|
||||
"peak": 25558,
|
||||
"rms": 3815.13,
|
||||
"seed": 232,
|
||||
"sha256": "0b836bfcf01aea73587dcbb5a26460dbf6153acc03732fe0c047401a62ad2dfa"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Resize saved generated masters and synthesize reproducible package WAVs.
|
||||
|
||||
No original assets are copied. Canvas resampling retains the master's true alpha;
|
||||
art is never redrawn. Run from any directory with Python 3 and Pillow installed.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from PIL import Image
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import random
|
||||
import struct
|
||||
import wave
|
||||
|
||||
ROOT = Path(__file__).resolve().parent
|
||||
RATE = 22050
|
||||
|
||||
|
||||
def sha(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def artwork():
|
||||
stats = []
|
||||
for name, size, scale in [('unit', (96, 64), 8), ('projectile', (8, 8), 8)]:
|
||||
path = ROOT / 'art' / (name + '-generated-v1.png')
|
||||
original = Image.open(path).convert('RGBA')
|
||||
assert original.getchannel('A').getextrema()[0] == 0
|
||||
if name == 'unit':
|
||||
assert original.width * size[1] == original.height * size[0]
|
||||
hd = original.resize((size[0] * scale, size[1] * scale), Image.Resampling.LANCZOS)
|
||||
else:
|
||||
# Keep the complete square alpha canvas; this preserves every source pixel.
|
||||
side = max(original.size)
|
||||
canvas = Image.new('RGBA', (side, side), (0, 0, 0, 0))
|
||||
canvas.alpha_composite(original, ((side-original.width)//2, (side-original.height)//2))
|
||||
hd = canvas.resize((size[0] * scale, size[1] * scale), Image.Resampling.LANCZOS)
|
||||
sd = hd.resize(size, Image.Resampling.BOX)
|
||||
for mode, sprite in [('sd', sd), ('hd', hd)]:
|
||||
alpha = sprite.getchannel('A')
|
||||
mask = alpha.point(lambda a: 255 if a >= 128 else 0)
|
||||
bounds = mask.getbbox()
|
||||
assert bounds and bounds[0] > 0 and bounds[1] > 0
|
||||
assert bounds[2] < sprite.width and bounds[3] < sprite.height
|
||||
out = ROOT / 'art' / (name + '-' + mode + '.png')
|
||||
sprite.save(out)
|
||||
stats.append({'file': str(out.relative_to(ROOT)), 'size': list(sprite.size),
|
||||
'opaque_bounds': list(bounds), 'source': str(path.relative_to(ROOT)),
|
||||
'alpha_min_max': list(alpha.getextrema()), 'sha256': sha(out)})
|
||||
(ROOT / 'art-qa.json').write_text(json.dumps({
|
||||
'logical_unit': [96, 64], 'logical_projectile': [8, 8], 'hd_scale': 8,
|
||||
'frames': 1, 'alpha': 'True RGBA; runtime palette mapping uses alpha >= 128',
|
||||
'conversion': 'Full source canvas Lanczos HD, BOX SD; no sprite redraw or catalog copies',
|
||||
'files': stats}, indent=2) + '\n')
|
||||
|
||||
|
||||
def audio():
|
||||
stats = []
|
||||
for name, duration, seed in [('shot', 0.21, 212), ('warning', 0.36, 222), ('death', 0.95, 232)]:
|
||||
rng = random.Random(seed)
|
||||
count = int(duration * RATE)
|
||||
phase = 0.0
|
||||
low_noise = 0.0
|
||||
signal = []
|
||||
for n in range(count):
|
||||
t = n / RATE
|
||||
p = n / max(1, count-1)
|
||||
noise = rng.uniform(-1, 1)
|
||||
low_noise = low_noise * .86 + noise * .14
|
||||
if name == 'warning':
|
||||
# Two brief rising radar pips separated by a clear gap.
|
||||
local = t % .18
|
||||
frequency = 1450 + 900 * min(1, local / .11)
|
||||
phase += 2 * math.pi * frequency / RATE
|
||||
envelope = (math.sin(math.pi * min(1, local / .12)) ** 2 if local < .12 else 0)
|
||||
value = .68 * math.sin(phase) + .15 * math.sin(phase * 2)
|
||||
elif name == 'shot':
|
||||
phase += 2 * math.pi * (170 + 710 * (1-p)**2) / RATE
|
||||
value = .60*math.sin(phase) + .22*math.sin(phase*1.52) + .12*noise
|
||||
envelope = min(1, t / .003) * math.exp(-p*6.0)
|
||||
else:
|
||||
phase += 2 * math.pi * (80 - 55*p) / RATE
|
||||
value = .65*low_noise + .20*noise + .26*math.sin(phase)
|
||||
envelope = min(1, t / .004) * math.exp(-p*4.5)
|
||||
envelope *= min(1, (count-1-n) / (RATE*.005))
|
||||
signal.append(value * max(0, envelope))
|
||||
maximum = max(abs(x) for x in signal)
|
||||
pcm = [round(v / maximum * 0.78 * 32767) for v in signal]
|
||||
path = ROOT / 'audio' / (name + '.wav')
|
||||
with wave.open(str(path), 'wb') as wav:
|
||||
wav.setnchannels(1); wav.setsampwidth(2); wav.setframerate(RATE)
|
||||
wav.writeframes(struct.pack('<' + 'h'*len(pcm), *pcm))
|
||||
stats.append({'file': str(path.relative_to(ROOT)), 'sample_rate': RATE,
|
||||
'channels': 1, 'bits': 16, 'samples': count, 'duration': duration,
|
||||
'peak': max(abs(x) for x in pcm),
|
||||
'rms': round(math.sqrt(sum(x*x for x in pcm)/len(pcm)),2),
|
||||
'seed': seed, 'sha256': sha(path)})
|
||||
(ROOT / 'audio-recipe.json').write_text(json.dumps({
|
||||
'generator': 'build_assets.py deterministic chirp/noise synthesis; no recordings',
|
||||
'files': stats}, indent=2) + '\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
artwork()
|
||||
audio()
|
||||
print('Generated Relay Warden SD/HD art and three non-silent WAVs')
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"weapons": [
|
||||
{
|
||||
"id": "iron_coast_boss_2:projectile",
|
||||
"name": "Relay Warden radar pulse",
|
||||
"damage": 3,
|
||||
"speed": 4,
|
||||
"cooldown": 54,
|
||||
"behavior": "straight",
|
||||
"target": "all",
|
||||
"art": {
|
||||
"png": "art/projectile-sd.png",
|
||||
"hd_png": "art/projectile-hd.png",
|
||||
"logical_width": 8,
|
||||
"logical_height": 8
|
||||
}
|
||||
}
|
||||
],
|
||||
"enemies": [
|
||||
{
|
||||
"id": "iron_coast_boss_2:unit",
|
||||
"name": "Relay Warden",
|
||||
"health": 160,
|
||||
"speed": 0,
|
||||
"reward": 1800,
|
||||
"ground": false,
|
||||
"boss": true,
|
||||
"cooldown": 54,
|
||||
"art": {
|
||||
"png": "art/unit-sd.png",
|
||||
"hd_png": "art/unit-hd.png",
|
||||
"logical_width": 96,
|
||||
"logical_height": 64,
|
||||
"frames": 1
|
||||
},
|
||||
"script": "scripts/ai.rhai"
|
||||
}
|
||||
],
|
||||
"sounds": [
|
||||
{ "id": "iron_coast_boss_2:shot", "name": "Relay Warden pulse volley", "file": "audio/shot.wav", "volume": 95, "pan": 127, "pitch": 128, "priority": 2 },
|
||||
{ "id": "iron_coast_boss_2:warning", "name": "Relay Warden radar charge", "file": "audio/warning.wav", "volume": 86, "pan": 127, "pitch": 128, "priority": 2 },
|
||||
{ "id": "iron_coast_boss_2:death", "name": "Relay Warden destruction", "file": "audio/death.wav", "volume": 120, "pan": 127, "pitch": 128, "priority": 4 }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
id = "iron_coast_boss_2"
|
||||
name = "Iron Coast: Relay Warden"
|
||||
version = "1.0.0"
|
||||
api_version = 1
|
||||
description = "A broad radar-aircraft boss for Tidal Relay, with an audible warning and alternating two-lane pulse volleys. Includes transparent SD/HD art and generated sounds."
|
||||
content = "content.json"
|
||||
scripts = ["scripts/ai.rhai"]
|
||||
@@ -0,0 +1,5 @@
|
||||
Use case: stylized-concept
|
||||
Asset type: one transparent projectile sprite for Relay Warden radar-aircraft boss in Iron Coast top-down shooter.
|
||||
Primary request: small compact cyan radar pulse, a vertically pointed metallic dart with dark gunmetal center and bright cyan electrical collar arranged around it. Directly overhead, dart nose points DOWN. Flat orthographic view, narrow central dart inside a short cyan oval electrical sheath. Restrained bright cyan body, white small central core, dark gunmetal stub fins; the silhouette stays compact and readable at 8x8 logical pixels. No long trails. This is different from a fireball or explosion.
|
||||
Composition: one whole projectile centered in a square transparent canvas, with 18% blank margin all sides.
|
||||
Constraints: genuinely transparent RGBA background; no text, no logo, no scene, no checkerboard artwork, no exterior shadow, no second projectile. Preserve crisp self-contained alpha silhouette.
|
||||
@@ -0,0 +1,7 @@
|
||||
Use case: stylized-concept
|
||||
Asset type: genuinely transparent HD boss sprite for top-down 2D vertical scrolling shooter; Relay Warden, new Iron Coast electronic warfare boss.
|
||||
Input reference image: inspected Iron Coast Command Cruiser is a STYLE reference only; create a DIFFERENT silhouette, a broad aircraft with wingspan rather than a narrow cruiser.
|
||||
Primary request: one large broad industrial radar aircraft, strict orthographic view directly above, pointed nose facing DOWN the image toward bottom edge. Strong symmetrical swept armored wing spanning horizontally, gunmetal grey metal plates, tiny dark red markings, a prominent circular radar dish centered on the upper fuselage, two orange engine exhaust slots facing up, two downward-facing cannon muzzles integrated into wing roots. Radar dish is a substantial physical armored circular fixture, not glowing energy. Heavy believable 1990s Raptor-inspired industrial aircraft design with crisp edge detail and restrained textures.
|
||||
Composition: single whole sprite centered, exact full canvas aspect 3:2, aircraft fills roughly 82% canvas width and 82% canvas height with clear transparent margin on every side, nose down, whole wings and tail included. Flat overhead lighting from upper left; no perspective, no horizon.
|
||||
Palette: gunmetal and slate grey, restrained dark red panels, amber indicators.
|
||||
Constraints: true RGBA transparent background, no shadow outside aircraft, no text, no labels, no logo, no watermark, no scene, no additional sprites, no tile grid. No white/matte fringe. Ship should remain readable when reduced to 96 by 64 pixels.
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"api_version": 1,
|
||||
"mod_version": "1.0.0",
|
||||
"tool": "built-in image_gen.imagegen via imagegen skill",
|
||||
"skill": "/home/quintine/.codex/skills/.system/imagegen/SKILL.md",
|
||||
"generated_at": "2026-10-10",
|
||||
"style_reference": {
|
||||
"path": "../iron_coast_boss/art/unit-hd.png",
|
||||
"role": "Inspected generated Iron Coast boss artwork used only as material/style reference; new broad radar aircraft silhouette and unique projectile generated independently",
|
||||
"original_catalog_pixels_copied": false
|
||||
},
|
||||
"assets": [
|
||||
{
|
||||
"file": "art/unit-generated-v1.png",
|
||||
"prompt": "prompts/unit-hd-v1.txt",
|
||||
"tool_output": "exec-28f23852-b44b-4461-89d2-eb72c6e150a0.png",
|
||||
"sha256": "ef00f35f6df18d5590e7c64168fa071daf31b116dca36f86542f1852865ce5ab"
|
||||
},
|
||||
{
|
||||
"file": "art/projectile-generated-v1.png",
|
||||
"prompt": "prompts/projectile-hd-v1.txt",
|
||||
"tool_output": "exec-7200b663-a79e-4899-a062-3d06a7bae851.png",
|
||||
"sha256": "51fe35c238b299fbcac748278e6799c6e4bfe30439471ad064d8a696ca97b147"
|
||||
}
|
||||
],
|
||||
"pipeline": "build_assets.py: full-canvas alpha-preserving Lanczos HD / BOX SD, deterministic 22050Hz 16-bit mono sound synthesis",
|
||||
"version_control": "All generated masters, shipped SD/HD PNGs, WAVs, prompts and provenance tracked in Git per user request"
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// All cursors are per entity. Arrival and attack timers use simulation callbacks.
|
||||
fn is_alive(event) {
|
||||
for unit in event.entities {
|
||||
if unit.entity == event.entity && unit.health > 0 { return true; }
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn on_spawn(event, state, random) {
|
||||
// Register the life before checking current liveness: the engine can kill a
|
||||
// queued spawn during advance, before it dispatches this callback. Its later
|
||||
// actual death still needs one objective update, without movement or audio.
|
||||
if !state.contains("directions") { state.directions = #{}; }
|
||||
if !state.contains("ages") { state.ages = #{}; }
|
||||
let key = event.entity.to_string();
|
||||
state.directions[key] = 1;
|
||||
state.ages[key] = 0;
|
||||
if !is_alive(event) { return #{ commands: [], state: state }; }
|
||||
#{ commands: [#{ op: "velocity", entity: event.entity, vx: 0, vy: 2 }], state: state }
|
||||
}
|
||||
|
||||
fn on_tick(event, state, random) {
|
||||
if event.health <= 0 || !is_alive(event) { return #{ commands: [], state: state }; }
|
||||
if event.y < 24 {
|
||||
return #{ commands: [#{ op: "velocity", entity: event.entity, vx: 0, vy: 2 }], state: state };
|
||||
}
|
||||
let key = event.entity.to_string();
|
||||
// An untracked unit cannot acquire another entity's attack timer.
|
||||
if !state.contains("ages") || !state.ages.contains(key) {
|
||||
return #{ commands: [], state: state };
|
||||
}
|
||||
let direction = state.directions[key];
|
||||
if event.x >= 144 { direction = -1; }
|
||||
if event.x <= 80 { direction = 1; }
|
||||
state.directions[key] = direction;
|
||||
let age = state.ages[key] + 1;
|
||||
state.ages[key] = age;
|
||||
let commands = [
|
||||
#{ op: "move", entity: event.entity, x: event.x, y: 24 },
|
||||
#{ op: "velocity", entity: event.entity, vx: direction, vy: 0 }
|
||||
];
|
||||
// Twelve ticks of warning precede the shot: about half a second at 70/3 Hz.
|
||||
if age % 54 == 42 {
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_2:warning" });
|
||||
}
|
||||
if age % 54 == 0 {
|
||||
// Alternate parallel outer lanes with a diverging inner fork.
|
||||
// Both leave a readable opening directly under the central radar dish.
|
||||
if (age / 54) % 2 == 1 {
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_2:projectile", x: event.x + 24, y: 75, vx: 0, vy: 4, hostile: true });
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_2:projectile", x: event.x + 72, y: 75, vx: 0, vy: 4, hostile: true });
|
||||
} else {
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_2:projectile", x: event.x + 36, y: 75, vx: -1, vy: 4, hostile: true });
|
||||
commands.push(#{ op: "projectile", weapon: "iron_coast_boss_2:projectile", x: event.x + 60, y: 75, vx: 1, vy: 4, hostile: true });
|
||||
}
|
||||
commands.push(#{ op: "play_sound", sound: "iron_coast_boss_2:shot" });
|
||||
state.shots = if state.contains("shots") { state.shots + 1 } else { 1 };
|
||||
}
|
||||
#{ commands: commands, state: state }
|
||||
}
|
||||
|
||||
fn on_hit(event, state, random) {
|
||||
state.hits = if state.contains("hits") { state.hits + 1 } else { 1 };
|
||||
#{ commands: [], state: state }
|
||||
}
|
||||
|
||||
fn on_death(event, state, random) {
|
||||
let key = event.entity.to_string();
|
||||
// A death callback must refer to a spawned unit that is absent from live queries.
|
||||
// Removing its cursor also makes repeated death notifications harmless.
|
||||
if is_alive(event) || !state.contains("ages") || !state.ages.contains(key) {
|
||||
return #{ commands: [], state: state };
|
||||
}
|
||||
state.ages.remove(key);
|
||||
state.directions.remove(key);
|
||||
state.destroyed = if state.contains("destroyed") { state.destroyed + 1 } else { 1 };
|
||||
let defeated = if event.objectives.contains("iron_coast_boss_2:defeated") { event.objectives["iron_coast_boss_2:defeated"] + 1 } else { 1 };
|
||||
#{ commands: [
|
||||
#{ op: "objective", key: "iron_coast_boss_2:defeated", value: defeated },
|
||||
#{ op: "effect", x: event.x + 32, y: event.y + 38 },
|
||||
#{ op: "effect", x: event.x + 64, y: event.y + 38 },
|
||||
#{ op: "play_sound", sound: "iron_coast_boss_2:death" }
|
||||
], state: state }
|
||||
}
|
||||
@@ -0,0 +1,822 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"const-random",
|
||||
"getrandom 0.3.4",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.9.1",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.25.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"moxcms",
|
||||
"num-traits",
|
||||
"png",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iron-coast-boss-2-validation"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"raptor-replica",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.190"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "moxcms"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"pxfm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "no-std-compat"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c"
|
||||
dependencies = [
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
"miniz_oxide 0.8.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "raptor-replica"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"image",
|
||||
"rhai",
|
||||
"schemars",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai"
|
||||
version = "1.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0334639972c0ea5a3fd366aa36116754a11431b619fec3ed559b3f73bcbcebf5"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bitflags",
|
||||
"no-std-compat",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rhai_codegen",
|
||||
"serde",
|
||||
"smallvec",
|
||||
"smartstring",
|
||||
"thin-vec",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rhai_codegen"
|
||||
version = "3.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cd3a7535e50bf36857e7be7bec276d334e8c2dfa469c2201226fd01638ea5ca"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive_internals"
|
||||
version = "0.29.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.151"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.16.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smartstring"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"static_assertions",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "static_assertions"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6a4b9ba8738cb4a4f399d37e266becfd475e75eb73425b87a05a2f2039ba63e"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_write",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_write"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.4+wasi-0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 3.0.6",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.129"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.62"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "iron-coast-boss-2-validation"
|
||||
version = "1.0.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
raptor-replica = { path = "../../..", default-features = false }
|
||||
serde_json = "1"
|
||||
anyhow = "1"
|
||||
|
||||
[[bin]]
|
||||
name = "validate-boss"
|
||||
path = "validate.rs"
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"actual_cues": [
|
||||
"iron_coast_boss_2:shot",
|
||||
"iron_coast_boss_2:warning"
|
||||
],
|
||||
"actual_engine_tick": 271,
|
||||
"dead_or_repeated_callbacks": "no commands",
|
||||
"death_objective": 1,
|
||||
"first_volley": [
|
||||
[
|
||||
136,
|
||||
75,
|
||||
0,
|
||||
4,
|
||||
true,
|
||||
"iron_coast_boss_2:projectile"
|
||||
],
|
||||
[
|
||||
184,
|
||||
75,
|
||||
0,
|
||||
4,
|
||||
true,
|
||||
"iron_coast_boss_2:projectile"
|
||||
]
|
||||
],
|
||||
"mod": "iron_coast_boss_2",
|
||||
"outcome": "Playing",
|
||||
"package_validation": "passed; PNGs, sound decoding, manifest and script",
|
||||
"per_entity_state_cleanup": "passed",
|
||||
"queued_spawn_step_lethal_projectile": "passed",
|
||||
"reused_entity_id_new_life": "counts once",
|
||||
"reward": 1800,
|
||||
"second_volley": [
|
||||
[
|
||||
148,
|
||||
75,
|
||||
-1,
|
||||
4,
|
||||
true,
|
||||
"iron_coast_boss_2:projectile"
|
||||
],
|
||||
[
|
||||
172,
|
||||
75,
|
||||
1,
|
||||
4,
|
||||
true,
|
||||
"iron_coast_boss_2:projectile"
|
||||
]
|
||||
],
|
||||
"volley_ages": [
|
||||
54,
|
||||
108
|
||||
],
|
||||
"warning_ages": [
|
||||
42,
|
||||
96
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
//! Standalone mod acceptance without original copyrighted game data.
|
||||
use anyhow::{Result, ensure};
|
||||
use raptor_replica::{
|
||||
content::{ContentId, ContentPack, Registry},
|
||||
modding::{self, ActiveMods, Command, ScriptRuntime},
|
||||
persistence::Pilot,
|
||||
simulation::{Input, Outcome, Projectile, World},
|
||||
};
|
||||
use serde_json::{Value, json};
|
||||
use std::path::PathBuf;
|
||||
const OWNER: &str = "iron_coast_boss_2";
|
||||
const SCRIPT: &str = "scripts/ai.rhai";
|
||||
const STATE: &str = "iron_coast_boss_2/scripts/ai.rhai";
|
||||
const OBJECTIVE: &str = "iron_coast_boss_2:defeated";
|
||||
|
||||
fn event(entity: u64, health: i32, x: i32, y: i32) -> Value {
|
||||
json!({"entity":entity,"health":health,"x":x,"y":y,"tick":900,
|
||||
"objectives":{},"entities":[{"entity":entity,"health":health,"x":x,"y":y}]})
|
||||
}
|
||||
fn invoke(runtime: &mut ScriptRuntime, hook: &str, event: Value) -> Result<Vec<Command>> {
|
||||
runtime.begin_tick();
|
||||
runtime.invoke(OWNER, SCRIPT, hook, &event, 31)
|
||||
}
|
||||
fn main() -> Result<()> {
|
||||
let mods = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../..")
|
||||
.canonicalize()?;
|
||||
let mut base = Registry::default();
|
||||
let content: ContentPack = serde_json::from_value(json!({
|
||||
"weapons":[{"id":"base:projectile_forward_guns","name":"Forward guns","damage":1000,"speed":8,"cooldown":2,"behavior":"straight","target":"all"}],
|
||||
"items":[{"id":"base:forward_guns","name":"Forward guns","price":0,"weapon":"base:projectile_forward_guns"},
|
||||
{"id":"base:energy","name":"Shield","price":0,"max_count":100}],
|
||||
"missions":[{"id":"base:sector_1_wave_1","name":"Boss test","duration":10000,"auto_complete":false,
|
||||
"spawns":[{"tick":1,"enemy":"iron_coast_boss_2:unit","x":112,"y":-64}]}]
|
||||
}))?;
|
||||
base.apply(content, "base")?;
|
||||
let discovery = modding::discover(&mods);
|
||||
let mut active = ActiveMods::build(&base, &discovery, &[OWNER.into()], None)?;
|
||||
let def = &active.registry.enemies[&ContentId::from("iron_coast_boss_2:unit")];
|
||||
ensure!(def.health == 160 && def.reward == 1800 && def.boss && def.speed == 0);
|
||||
let rt = &mut active.scripts;
|
||||
let mut cues = Vec::new();
|
||||
invoke(rt, "on_spawn", event(1, 160, 112, 24))?;
|
||||
let mut first = Vec::new();
|
||||
let mut second = Vec::new();
|
||||
for age in 1..=108 {
|
||||
let commands = invoke(rt, "on_tick", event(1, 160, 112, 24))?;
|
||||
for command in &commands {
|
||||
if let Command::PlaySound { sound } = command {
|
||||
cues.push((age, sound.clone()));
|
||||
}
|
||||
}
|
||||
let shots: Vec<_> = commands
|
||||
.iter()
|
||||
.filter_map(|command| {
|
||||
if let Command::Projectile {
|
||||
x,
|
||||
y,
|
||||
vx,
|
||||
vy,
|
||||
hostile,
|
||||
weapon,
|
||||
} = command
|
||||
{
|
||||
Some((*x, *y, *vx, *vy, *hostile, weapon.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if age == 54 {
|
||||
first = shots;
|
||||
} else if age == 108 {
|
||||
second = shots;
|
||||
} else {
|
||||
ensure!(shots.is_empty(), "unexpected volley at age {age}");
|
||||
}
|
||||
}
|
||||
ensure!(first.len() == 2 && second.len() == 2 && first != second);
|
||||
ensure!(first.iter().all(|p| p.2 == 0 && p.3 == 4 && p.4));
|
||||
ensure!(second[0].2 == -1 && second[1].2 == 1);
|
||||
ensure!(
|
||||
cues == vec![
|
||||
(42, format!("{OWNER}:warning")),
|
||||
(54, format!("{OWNER}:shot")),
|
||||
(96, format!("{OWNER}:warning")),
|
||||
(108, format!("{OWNER}:shot"))
|
||||
]
|
||||
);
|
||||
invoke(rt, "on_spawn", event(2, 160, 112, 24))?;
|
||||
ensure!(rt.states[STATE]["ages"]["2"] == 0);
|
||||
ensure!(
|
||||
invoke(rt, "on_death", event(1, 160, 112, 24))?.is_empty(),
|
||||
"living unit cannot register a death"
|
||||
);
|
||||
ensure!(
|
||||
invoke(rt, "on_tick", event(1, 0, 112, 24))?.is_empty(),
|
||||
"dead unit cannot fire or warn"
|
||||
);
|
||||
let mut death = event(1, 0, 112, 24);
|
||||
death["entities"] = json!([{"entity":2,"health":160}]);
|
||||
let commands = invoke(rt, "on_death", death.clone())?;
|
||||
ensure!(
|
||||
commands
|
||||
.iter()
|
||||
.any(|c| matches!(c,Command::Objective{key,value} if key==OBJECTIVE && *value==1))
|
||||
);
|
||||
ensure!(commands.iter().all(|c| !matches!(c, Command::Complete)));
|
||||
ensure!(
|
||||
invoke(rt, "on_death", death)?.is_empty(),
|
||||
"duplicate death cannot increment objective"
|
||||
);
|
||||
ensure!(rt.states[STATE]["ages"].get("1").is_none());
|
||||
ensure!(
|
||||
rt.states[STATE]["ages"]["2"] == 0,
|
||||
"another entity retains independent cursor"
|
||||
);
|
||||
let mut death2 = event(2, 0, 112, 24);
|
||||
death2["entities"] = json!([]);
|
||||
death2["objectives"] = json!({OBJECTIVE:1});
|
||||
let commands = invoke(rt, "on_death", death2)?;
|
||||
ensure!(
|
||||
commands
|
||||
.iter()
|
||||
.any(|c| matches!(c,Command::Objective{key,value} if key==OBJECTIVE && *value==2))
|
||||
);
|
||||
ensure!(rt.states[STATE]["ages"].as_object().unwrap().is_empty());
|
||||
ensure!(
|
||||
rt.states[STATE]["directions"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
|
||||
// Run actual engine hooks and command application; no scripted mission completion.
|
||||
active = ActiveMods::build(&base, &discovery, &[OWNER.into()], None)?;
|
||||
let pilot = Pilot::default();
|
||||
let mut world = World::new(&pilot.mission, &pilot, &active.registry)?;
|
||||
let mut actual_cues = std::collections::BTreeSet::new();
|
||||
for _ in 0..150 {
|
||||
world.step(Input::default(), &active.registry, &mut active.scripts)?;
|
||||
for cue in &world.mod_sounds {
|
||||
actual_cues.insert(cue.0.clone());
|
||||
}
|
||||
}
|
||||
ensure!(world.enemies.len() == 1 && world.enemies[0].y == 24);
|
||||
ensure!(world.enemies[0].x >= 79 && world.enemies[0].x <= 145);
|
||||
ensure!(
|
||||
actual_cues.contains("iron_coast_boss_2:warning")
|
||||
&& actual_cues.contains("iron_coast_boss_2:shot")
|
||||
);
|
||||
let initial_credits = world.player.credits;
|
||||
world.enemies[0].health = 0;
|
||||
world.step(Input::default(), &active.registry, &mut active.scripts)?;
|
||||
ensure!(world.objectives[OBJECTIVE] == 1 && world.enemies.is_empty());
|
||||
ensure!(world.player.credits == initial_credits + 1800);
|
||||
ensure!(
|
||||
world.outcome == Outcome::Playing,
|
||||
"boss never completes the mission itself"
|
||||
);
|
||||
let shots = active.scripts.states[STATE]["shots"].clone();
|
||||
for _ in 0..120 {
|
||||
world.step(Input::default(), &active.registry, &mut active.scripts)?;
|
||||
}
|
||||
ensure!(world.objectives[OBJECTIVE] == 1 && active.scripts.states[STATE]["shots"] == shots);
|
||||
ensure!(
|
||||
active.scripts.states[STATE]["ages"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
// A projectile can kill a scheduled spawn during advance, before on_spawn dispatch.
|
||||
// Use an actual stationary projectile inside the arriving boss silhouette.
|
||||
let mut early_active = ActiveMods::build(&base, &discovery, &[OWNER.into()], None)?;
|
||||
let mut early_world = World::new(&pilot.mission, &pilot, &early_active.registry)?;
|
||||
early_world.step(
|
||||
Input::default(),
|
||||
&early_active.registry,
|
||||
&mut early_active.scripts,
|
||||
)?;
|
||||
early_world.projectiles.push(Projectile {
|
||||
entity: 9000,
|
||||
weapon: ContentId::from("base:projectile_forward_guns"),
|
||||
x: 160,
|
||||
y: -40,
|
||||
px: 160,
|
||||
py: -40,
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
hostile: false,
|
||||
age: 0,
|
||||
legacy: None,
|
||||
legacy_enemy: None,
|
||||
});
|
||||
early_world.step(
|
||||
Input::default(),
|
||||
&early_active.registry,
|
||||
&mut early_active.scripts,
|
||||
)?;
|
||||
ensure!(
|
||||
early_world.enemies.len() == 1 && early_world.enemies[0].health <= 0,
|
||||
"lethal projectile must kill the boss in its spawn step"
|
||||
);
|
||||
early_world.step(
|
||||
Input::default(),
|
||||
&early_active.registry,
|
||||
&mut early_active.scripts,
|
||||
)?;
|
||||
ensure!(
|
||||
early_world.objectives.get(OBJECTIVE) == Some(&1),
|
||||
"queued spawn-step kill must increment its actual-death objective"
|
||||
);
|
||||
ensure!(
|
||||
early_active.scripts.states[STATE]["ages"]
|
||||
.as_object()
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
// Reusing an entity ID in a new mission must count its new life, while another
|
||||
// death notification for that life must stay harmless.
|
||||
let rt = &mut early_active.scripts;
|
||||
invoke(rt, "on_spawn", event(1, 160, 112, 24))?;
|
||||
let mut reused = event(1, 0, 112, 24);
|
||||
reused["entities"] = json!([]);
|
||||
reused["objectives"] = json!({OBJECTIVE: 1});
|
||||
let reused_commands = invoke(rt, "on_death", reused.clone())?;
|
||||
ensure!(reused_commands.iter().any(|c| matches!(c,
|
||||
Command::Objective { key, value } if key == OBJECTIVE && *value == 2)));
|
||||
ensure!(invoke(rt, "on_death", reused)?.is_empty());
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string_pretty(&json!({
|
||||
"mod":OWNER,"package_validation":"passed; PNGs, sound decoding, manifest and script",
|
||||
"warning_ages":[42,96],"volley_ages":[54,108],"first_volley":first,"second_volley":second,
|
||||
"actual_engine_tick":world.tick,"death_objective":world.objectives[OBJECTIVE],
|
||||
"reward":world.player.credits-initial_credits,"outcome":format!("{:?}",world.outcome),
|
||||
"per_entity_state_cleanup":"passed","dead_or_repeated_callbacks":"no commands",
|
||||
"actual_cues":actual_cues,
|
||||
"queued_spawn_step_lethal_projectile":"passed",
|
||||
"reused_entity_id_new_life":"counts once"
|
||||
}))?
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||