Retune Iron Coast waves, enemy attacks and weapon presentation
This commit is contained in:
1 parent
00cfcc2031
commit
c4a4d821d5
145 files changed
+9242
-2951
No files matched your search
@@ -1,9 +1,21 @@
|
||||
# Seeker Swarm 1.0.0
|
||||
# Seeker Swarm
|
||||
|
||||
Enable **Seeker Swarm** in Mods and apply at the main menu. This independent API 1 example has no dependencies. Buy its equipment in the shop (285,000 credits, maximum one) or collect its pickup. Choose it in Inventory or cycle special weapons during a mission. The equipped weapon fires with Fire or Special; it consumes no ammunition.
|
||||
API 1 package, version 1.1.0. Enable it in Mods at the main menu and apply the selection. This package has no mod dependencies.
|
||||
|
||||
Projectile: 6 damage, 6 logical pixels per step, 18 step cooldown, `homing`, air and ground targets. Three homing missiles fire together; an impact deals two fragmentation damage to up to two nearby enemies. Rhai stores bounded burst/hit counters and emits local firing/impact sounds and effects.
|
||||
Selectable reusable special. Fires three homing missiles at air or ground enemies. Hits scatter fragments into up to two nearby enemies. Select it in Inventory or cycle specials, then use Fire or Special.
|
||||
|
||||
`art/equipment-sd.png` is a 16×16 logical icon, `art/equipment-hd.png` its detailed 4× variant. Projectile canvases are 8×16 SD and 32×64 HD. Switch HD artwork in Options. The generated master images, SD/HD sprites, WAV files, provenance and reproduction inputs are tracked in Git. Original game assets and decoded reference images remain outside version control. `python3 generate_audio.py` reproduces both sound files.
|
||||
Buy **Seeker Swarm** for **285,000 credits**; carry at most 1. Select it through Inventory or the Cycle control when it is a special weapon.
|
||||
|
||||
Validate locally with `cargo run --no-default-features -- --mods mods --validate-mods seeker_swarm`. Read the shared [API documentation](../../docs/modding-api.md) for save pins, dependencies and sandbox limits.
|
||||
`seeker_swarm:projectile`: 6 damage, speed 6, 18-tick cooldown, `homing` behavior.
|
||||
|
||||
## Feedback
|
||||
|
||||
The **8×16 logical projectile canvas is retained**. Its visible SD silhouette is only 3×14; proportional downsampling can skip the thin centerline, so reducing it would harm readability. The original SD/HD PNG bytes remain unchanged. Three 6-damage missiles, 6px/tick speed, 18-tick cadence, homing and two-neighbor fragmentation are unchanged. Contact feedback uses a compact 4px arc for 2 ticks rather than a continuing explosion; coincident missile hits share the 3-tick flash and 6-tick audio gates. Fire/impact volume is 76/58. Price stays 285,000.
|
||||
|
||||
Only declared display sizing, cosmetic hit commands and playback levels/priorities change. No PNG/WAV pixels or samples, equipment icon dimensions, projectile collision rules, damage, cooldown, offsets, targeting, ammunition, capacity, controls or prices are changed. No new muzzle explosion or persistent trail is emitted. Normal original enemy deaths and terrain-destruction effects remain engine-controlled.
|
||||
|
||||
The three rapid weapons retain their prior burst/hit counters and reset only their cosmetic tick/projectile gates when mission ticks restart, accepting older saved script state without new fields. This version changes the package save pin; existing pilot compatibility confirmation remains the normal mod mechanism.
|
||||
|
||||
## Validation and history
|
||||
|
||||
See the [current Iron Coast retune results and limits](../../docs/iron-coast-retune.md). Package-local acceptance reports, validation results and retained test sources describe pre-retune 1.0 behavior unless a result explicitly identifies the newer package and runner. They remain historical material, not current playthrough guarantees.
|
||||
@@ -58,19 +58,19 @@
|
||||
"id": "seeker_swarm:fire",
|
||||
"name": "Seeker Swarm Fire",
|
||||
"file": "audio/fire.wav",
|
||||
"volume": 92,
|
||||
"volume": 76,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 1
|
||||
"priority": 2
|
||||
},
|
||||
{
|
||||
"id": "seeker_swarm:impact",
|
||||
"name": "Seeker Swarm Impact",
|
||||
"file": "audio/impact.wav",
|
||||
"volume": 78,
|
||||
"volume": 58,
|
||||
"pan": 127,
|
||||
"pitch": 128,
|
||||
"priority": 2
|
||||
"priority": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
id = "seeker_swarm"
|
||||
name = "Seeker Swarm"
|
||||
version = "1.0.0"
|
||||
version = "1.1.0"
|
||||
api_version = 1
|
||||
description = "Naval science-fiction seeker swarm: independent weapon, equipment, SD/HD artwork and local firing/impact audio."
|
||||
content = "content.json"
|
||||
|
||||
@@ -1,21 +1,42 @@
|
||||
// Built-in homing projectiles handle the normal volley and collision rules.
|
||||
// Firing callbacks add audio and counters; impact callbacks add hit visuals.
|
||||
// Firing callbacks add audio and counters; compact two-tick contact arcs are cosmetic.
|
||||
fn on_fire(event, state, random) {
|
||||
// Runtime script state can survive a mission change while tick/entity IDs restart.
|
||||
// Reset only cosmetic gates; the existing burst/hit counters keep their semantics.
|
||||
let last_fire = if state.contains("last_fire_tick") { state.last_fire_tick } else { -1 };
|
||||
let restarted = event.tick <= last_fire
|
||||
|| (state.contains("feedback_tick") && event.tick <= state.feedback_tick)
|
||||
|| (state.contains("sound_tick") && event.tick <= state.sound_tick);
|
||||
let bursts = if state.contains("bursts") { state.bursts % 100000 + 1 } else { 1 };
|
||||
let hits = if state.contains("hits") { state.hits } else { 0 };
|
||||
let commands = [
|
||||
#{ op: "play_sound", sound: "seeker_swarm:fire" }
|
||||
];
|
||||
#{ commands: commands, state: #{ bursts: bursts, hits: hits } }
|
||||
#{ commands: commands, state: #{ bursts: bursts, hits: hits,
|
||||
// A new volley cannot be the preceding beam's second hit tick. Forget its
|
||||
// entity ID, which may be reused in another mission even after a late first shot.
|
||||
feedback_projectile: -1,
|
||||
feedback_tick: if !restarted && state.contains("feedback_tick") { state.feedback_tick } else { -10 },
|
||||
sound_tick: if !restarted && state.contains("sound_tick") { state.sound_tick } else { -10 },
|
||||
last_fire_tick: event.tick } }
|
||||
}
|
||||
|
||||
fn on_hit(event, state, random) {
|
||||
let bursts = if state.contains("bursts") { state.bursts } else { 0 };
|
||||
let hits = if state.contains("hits") { state.hits % 100000 + 1 } else { 1 };
|
||||
let commands = [
|
||||
#{ op: "play_sound", sound: "seeker_swarm:impact" },
|
||||
#{ op: "effect", x: event.x, y: event.y }
|
||||
];
|
||||
// One compact contact flash per projectile, not one explosion per beam target.
|
||||
// A separate six-tick audio gate also bounds coincident paired/swarm hits.
|
||||
let previous_projectile = if state.contains("feedback_projectile") { state.feedback_projectile } else { -1 };
|
||||
let previous_tick = if state.contains("feedback_tick") { state.feedback_tick } else { -10 };
|
||||
let sound_tick = if state.contains("sound_tick") { state.sound_tick } else { -10 };
|
||||
let commands = [];
|
||||
let feedback = event.projectile != previous_projectile && event.tick - previous_tick >= 3;
|
||||
if feedback {
|
||||
commands.push(#{op: "arc", x: event.x - 2, y: event.y + 2,
|
||||
to_x: event.x + 2, to_y: event.y - 2, ticks: 2});
|
||||
}
|
||||
let audible = event.tick - sound_tick >= 6;
|
||||
if audible { commands.push(#{op: "play_sound", sound: "seeker_swarm:impact"}); }
|
||||
|
||||
// A small fragmentation charge affects at most two nearby surviving enemies.
|
||||
let fragments = 0;
|
||||
@@ -27,5 +48,9 @@ fn on_hit(event, state, random) {
|
||||
fragments += 1;
|
||||
}
|
||||
}
|
||||
#{ commands: commands, state: #{ bursts: bursts, hits: hits } }
|
||||
#{ commands: commands, state: #{ bursts: bursts, hits: hits,
|
||||
feedback_projectile: if feedback { event.projectile } else { previous_projectile },
|
||||
feedback_tick: if feedback { event.tick } else { previous_tick },
|
||||
sound_tick: if audible { event.tick } else { sound_tick },
|
||||
last_fire_tick: if state.contains("last_fire_tick") { state.last_fire_tick } else { -1 } } }
|
||||
}
|
||||
Reference in new issue
Block a user