767 lines
24 KiB
Rust
767 lines
24 KiB
Rust
use raptor_replica::{
|
|
content::{ContentId, ContentPack, Registry},
|
|
modding::{Command, ScriptRuntime},
|
|
persistence::Pilot,
|
|
simulation::{Input, LegacyProjectile, LineMove, Projectile, World},
|
|
};
|
|
use serde_json::json;
|
|
|
|
fn fixture() -> Registry {
|
|
let pack: ContentPack = serde_json::from_value(json!({
|
|
"weapons": [{"id":"base:projectile_forward_guns", "name":"Guns", "damage":1,"speed":8,"cooldown":2,"behavior":"straight","target":"all","legacy":0,"art":{"asset":42,"logical_width":4,"logical_height":8,"frames":4}}],
|
|
"items":[
|
|
{"id":"base:forward_guns","name":"Guns","price":0,"weapon":"base:projectile_forward_guns","legacy":0},
|
|
{"id":"base:energy","name":"Shield","price":0,"max_count":100,"legacy":16},
|
|
{"id":"base:super_shield","name":"Super Shield","price":0,"max_count":100,"legacy":15},
|
|
{"id":"base:medkit","name":"Medkit","price":0,"max_count":5,"special":true,"consumable":true,"shield":25,"script":"medkit.rhai"}
|
|
],
|
|
"enemies":[{"id":"base:drone","name":"Drone","speed":1,"health":20,"path":[[0,200]]}],
|
|
"missions":[{"id":"base:sector_1_wave_1","name":"Synthetic Mission","duration":1000}]
|
|
})).unwrap();
|
|
let mut reg = Registry::default();
|
|
reg.apply(pack, "base").unwrap();
|
|
reg.validate().unwrap();
|
|
reg
|
|
}
|
|
|
|
#[test]
|
|
fn demo_coordinates_bypass_normal_input_bounds() {
|
|
let reg = fixture();
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(
|
|
Input {
|
|
absolute: Some([284, 162]),
|
|
demo_bank: Some(3),
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!((world.player.x, world.player.y), (284, 162));
|
|
world
|
|
.step(
|
|
Input {
|
|
absolute: Some([284, 162]),
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!((world.player.x, world.player.y), (282, 160));
|
|
}
|
|
|
|
#[test]
|
|
fn original_guns_use_banked_muzzles_and_accelerate_after_position_snapshot() {
|
|
let reg = fixture();
|
|
let mut world = World::new(&"base:sector_1_wave_1".into(), &Pilot::default(), ®).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(
|
|
Input {
|
|
fire: true,
|
|
demo_bank: Some(3),
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
// Center (160,176), gun offsets +6/-7, sprite half-size (2,4).
|
|
assert_eq!(
|
|
world
|
|
.projectiles
|
|
.iter()
|
|
.map(|p| (p.x, p.y))
|
|
.collect::<Vec<_>>(),
|
|
[(164, 172), (151, 172)]
|
|
);
|
|
world
|
|
.step(
|
|
Input {
|
|
fire: true,
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.projectiles.len(), 2);
|
|
assert_eq!(world.projectiles[0].y, 163); // Speed rose from 8 to 9 after the first position.
|
|
world
|
|
.step(
|
|
Input {
|
|
fire: true,
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.projectiles.len(), 4);
|
|
assert_eq!(world.projectiles[0].y, 153);
|
|
}
|
|
|
|
#[test]
|
|
fn enemy_paths_stop_at_endpoint_but_shots_cross_it() {
|
|
let mut enemy = LineMove::new(0, 0, 2, 1);
|
|
enemy.advance(5);
|
|
assert_eq!((enemy.x, enemy.y), (2, 1));
|
|
let mut shot = LineMove::new(0, 0, 2, 1);
|
|
shot.advance_shot(5);
|
|
assert_eq!((shot.x, shot.y), (5, 3));
|
|
assert!(shot.done);
|
|
}
|
|
|
|
#[test]
|
|
fn mission_transition_preserves_remaining_super_shield() {
|
|
let reg = fixture();
|
|
let mut pilot = Pilot::default();
|
|
pilot.inventory.insert("base:super_shield".into(), 31);
|
|
let mut world = World::new(&pilot.mission, &pilot, ®).unwrap();
|
|
assert_eq!(world.player.super_shield, 31);
|
|
world
|
|
.apply_commands(
|
|
&[Command::Damage {
|
|
entity: 0,
|
|
amount: 12,
|
|
}],
|
|
®,
|
|
)
|
|
.unwrap();
|
|
let saved = world.pilot(&pilot);
|
|
assert_eq!(saved.inventory[&ContentId::from("base:super_shield")], 19);
|
|
assert_eq!(
|
|
World::new(&pilot.mission, &saved, ®)
|
|
.unwrap()
|
|
.player
|
|
.super_shield,
|
|
19
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn moving_an_entity_rebases_its_path() {
|
|
let reg = fixture();
|
|
let mut world = World::new(&"base:sector_1_wave_1".into(), &Pilot::default(), ®).unwrap();
|
|
let entity = world.spawn(&"base:drone".into(), 100, 0, ®).unwrap();
|
|
world
|
|
.apply_commands(
|
|
&[Command::Move {
|
|
entity,
|
|
x: 20,
|
|
y: 20,
|
|
}],
|
|
®,
|
|
)
|
|
.unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
world.step(Input::default(), ®, &mut scripts).unwrap();
|
|
assert_eq!((world.enemies[0].x, world.enemies[0].y), (20, 20));
|
|
world.step(Input::default(), ®, &mut scripts).unwrap();
|
|
assert_eq!((world.enemies[0].x, world.enemies[0].y), (20, 21));
|
|
}
|
|
|
|
#[test]
|
|
fn consumable_use_has_snapshot_and_only_fires_on_press() {
|
|
let reg = fixture();
|
|
let mut pilot = Pilot::default();
|
|
pilot.inventory.insert("base:medkit".into(), 2);
|
|
pilot.inventory.insert("base:energy".into(), 50);
|
|
pilot.selected = Some("base:medkit".into());
|
|
let mut world = World::new(&pilot.mission, &pilot, ®).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
scripts.add("base","medkit.rhai",r#"fn on_use(event,state,rng) {#{commands:[#{op:"credits",amount:7}], state:#{shield:event.player.shield,quantity:event.player.inventory["base:medkit"]}}}"#).unwrap();
|
|
world
|
|
.step(
|
|
Input {
|
|
special: true,
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.player.shield, 75);
|
|
assert_eq!(world.player.credits, 7);
|
|
assert_eq!(world.player.inventory[&ContentId::from("base:medkit")], 1);
|
|
world
|
|
.step(
|
|
Input {
|
|
special: true,
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.player.credits, 7);
|
|
world.step(Input::default(), ®, &mut scripts).unwrap();
|
|
world
|
|
.step(
|
|
Input {
|
|
special: true,
|
|
..Input::default()
|
|
},
|
|
®,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.player.shield, 100);
|
|
assert_eq!(world.player.credits, 14);
|
|
assert_eq!(world.player.selected, None);
|
|
}
|
|
|
|
fn loss_fixture() -> Registry {
|
|
let mut registry = fixture();
|
|
registry.apply(serde_json::from_value(json!({"items":[
|
|
{"id":"base:dumb_missile","name":"Dumb Missile","price":0,"special":true,"legacy":3},
|
|
{"id":"base:mini_gun","name":"Mini Gun","price":0,"special":true,"legacy":4},
|
|
{"id":"base:death_ray","name":"Death Ray","price":0,"special":true,"legacy":14},
|
|
{"id":"base:detect","name":"Detector","price":0,"legacy":17},
|
|
{"id":"base:itembuy6","name":"Cash Cargo","price":0,"max_count":50,"legacy":23}
|
|
]})).unwrap(), "base").unwrap();
|
|
registry
|
|
}
|
|
|
|
fn low_shield_world(registry: &Registry) -> (World, ScriptRuntime) {
|
|
let mut pilot = Pilot {
|
|
difficulty: 3,
|
|
..Pilot::default()
|
|
};
|
|
for id in [
|
|
"base:dumb_missile",
|
|
"base:mini_gun",
|
|
"base:death_ray",
|
|
"base:detect",
|
|
] {
|
|
pilot.inventory.insert(id.into(), 1);
|
|
}
|
|
pilot.inventory.insert("base:itembuy6".into(), 50);
|
|
let mut world = World::new(&pilot.mission, &pilot, registry).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(Input::default(), registry, &mut scripts)
|
|
.unwrap();
|
|
(world, scripts)
|
|
}
|
|
|
|
#[test]
|
|
fn low_shield_damage_loses_selected_weapon_then_cycles_in_legacy_order() {
|
|
let registry = loss_fixture();
|
|
let (mut world, mut scripts) = low_shield_world(®istry);
|
|
world.player.selected = Some("base:death_ray".into());
|
|
world
|
|
.apply_commands(
|
|
&[Command::Damage {
|
|
entity: 0,
|
|
amount: 92,
|
|
}],
|
|
®istry,
|
|
)
|
|
.unwrap();
|
|
let seed = world.rng.seed;
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.player.shield, 8);
|
|
assert!(
|
|
!world
|
|
.player
|
|
.inventory
|
|
.contains_key(&"base:death_ray".into())
|
|
);
|
|
assert_eq!(world.player.selected, Some("base:dumb_missile".into()));
|
|
assert_eq!(
|
|
world.player.inventory[&ContentId::from("base:itembuy6")],
|
|
50
|
|
);
|
|
assert!(world.sounds.contains(&119)); // FX_CRASH has no random pitch.
|
|
assert_eq!(world.rng.seed, seed);
|
|
|
|
world
|
|
.apply_commands(
|
|
&[Command::Damage {
|
|
entity: 0,
|
|
amount: 1,
|
|
}],
|
|
®istry,
|
|
)
|
|
.unwrap();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert!(
|
|
!world
|
|
.player
|
|
.inventory
|
|
.contains_key(&"base:dumb_missile".into())
|
|
);
|
|
assert_eq!(world.player.selected, Some("base:mini_gun".into()));
|
|
}
|
|
|
|
#[test]
|
|
fn low_shield_without_selection_loses_highest_legacy_item_and_protects_essentials() {
|
|
let registry = loss_fixture();
|
|
let (mut world, mut scripts) = low_shield_world(®istry);
|
|
world
|
|
.apply_commands(
|
|
&[Command::Damage {
|
|
entity: 0,
|
|
amount: 92,
|
|
}],
|
|
®istry,
|
|
)
|
|
.unwrap();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert!(!world.player.inventory.contains_key(&"base:itembuy6".into()));
|
|
for id in [
|
|
"base:forward_guns",
|
|
"base:energy",
|
|
"base:detect",
|
|
"base:death_ray",
|
|
] {
|
|
assert!(
|
|
world.player.inventory.contains_key(&id.into()),
|
|
"protected or lower-numbered item {id} lost"
|
|
);
|
|
}
|
|
assert_eq!(world.player.selected, None);
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert!(
|
|
world
|
|
.player
|
|
.inventory
|
|
.contains_key(&"base:death_ray".into())
|
|
);
|
|
assert!(!world.sounds.contains(&119)); // Remaining at low shield does not lose another item.
|
|
}
|
|
|
|
#[test]
|
|
fn low_energy_with_super_shield_and_damage_above_threshold_preserve_equipment() {
|
|
let registry = loss_fixture();
|
|
let (mut world, mut scripts) = low_shield_world(®istry);
|
|
world.player.selected = Some("base:death_ray".into());
|
|
world
|
|
.apply_commands(
|
|
&[Command::Damage {
|
|
entity: 0,
|
|
amount: 89,
|
|
}],
|
|
®istry,
|
|
)
|
|
.unwrap();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.player.shield, 11);
|
|
assert!(
|
|
world
|
|
.player
|
|
.inventory
|
|
.contains_key(&"base:death_ray".into())
|
|
);
|
|
world.player.super_shield = 5;
|
|
world
|
|
.apply_commands(&[Command::Shield { amount: -3 }], ®istry)
|
|
.unwrap();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.player.shield, 8);
|
|
assert!(
|
|
world
|
|
.player
|
|
.inventory
|
|
.contains_key(&"base:death_ray".into())
|
|
);
|
|
assert!(!world.sounds.contains(&119));
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_kamikaze_arms_chase_when_final_leg_starts_and_crosses_target() {
|
|
let mut registry = fixture();
|
|
let enemy = registry
|
|
.enemies
|
|
.get_mut(&ContentId::from("base:drone"))
|
|
.unwrap();
|
|
enemy.health = 10000;
|
|
enemy.speed = 4;
|
|
enemy.path = vec![[0, -114], [0, -96]];
|
|
let mut fields = vec![0; 26];
|
|
fields[7] = 1;
|
|
fields[9] = 1;
|
|
fields[18] = 4;
|
|
fields[19] = 2;
|
|
fields[21] = 2; // Original F_KAMI.
|
|
enemy.legacy = Some(raptor_replica::assets::LegacyEnemy {
|
|
name: "Synthetic Kamikaze".into(),
|
|
fields,
|
|
shoot_type: vec![0; 24],
|
|
engine_x: vec![0; 24],
|
|
engine_y: vec![0; 24],
|
|
engine_width: vec![0; 24],
|
|
gun_x: vec![0; 24],
|
|
gun_y: vec![0; 24],
|
|
flight_x: vec![0; 32],
|
|
flight_y: vec![0; 32],
|
|
});
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
let entity = world
|
|
.spawn(&"base:drone".into(), 48, -131, ®istry)
|
|
.unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
let mut armed = false;
|
|
let mut crossed = false;
|
|
for _ in 0..120 {
|
|
world
|
|
.step(
|
|
Input {
|
|
absolute: Some([96, 136]),
|
|
..Input::default()
|
|
},
|
|
®istry,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
let Some(enemy) = world.enemies.iter().find(|e| e.entity == entity) else {
|
|
break;
|
|
};
|
|
if enemy.kami == 1 {
|
|
armed = true;
|
|
assert_eq!(enemy.movement.ty, -12);
|
|
assert!(!enemy.movement.done);
|
|
assert_eq!(enemy.path_index, 1);
|
|
}
|
|
if enemy.kami == 2 {
|
|
assert_eq!((enemy.movement.tx, enemy.movement.ty), (112, 152));
|
|
crossed |= enemy.movement.y > 152;
|
|
}
|
|
}
|
|
assert!(armed, "last authored leg never armed chase");
|
|
assert!(crossed, "terminal chase stopped at its target");
|
|
assert!(
|
|
!world.enemies.iter().any(|e| e.entity == entity),
|
|
"terminal kamikaze failed to retire offscreen"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn legacy_beam_hits_launch_column_before_following_player_for_display() {
|
|
let mut registry = fixture();
|
|
registry.apply(serde_json::from_value(json!({
|
|
"weapons":[{"id":"base:projectile_death_ray","name":"Death Ray","damage":6,"speed":0,"cooldown":7,"behavior":"beam","target":"all","legacy":14,"art":{"asset":50,"logical_width":2,"logical_height":8,"frames":4}}],
|
|
"items":[{"id":"base:death_ray","name":"Death Ray","price":0,"special":true,"legacy":14,"weapon":"base:projectile_death_ray"}]
|
|
})).unwrap(), "base").unwrap();
|
|
let pilot = Pilot {
|
|
selected: Some("base:death_ray".into()),
|
|
inventory: [("base:death_ray".into(), 1), ("base:energy".into(), 100)]
|
|
.into_iter()
|
|
.collect(),
|
|
..Pilot::default()
|
|
};
|
|
let mut world = World::new(&pilot.mission, &pilot, ®istry).unwrap();
|
|
world
|
|
.spawn(&"base:drone".into(), 157, 40, ®istry)
|
|
.unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(
|
|
Input {
|
|
fire: true,
|
|
..Input::default()
|
|
},
|
|
®istry,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.enemies[0].health, 14);
|
|
world
|
|
.step(
|
|
Input {
|
|
absolute: Some([141, 160]),
|
|
..Input::default()
|
|
},
|
|
®istry,
|
|
&mut scripts,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(world.projectiles[0].x, 156); // Followed artwork is outside the enemy.
|
|
assert_eq!(world.enemies[0].health, 8); // Damage still tests launch column159.
|
|
}
|
|
|
|
fn collision_fixture() -> Registry {
|
|
let mut registry = fixture();
|
|
registry.apply(serde_json::from_value(json!({"weapons":[
|
|
{"id":"base:projectile_energy_grab","name":"Energy Grab","damage":3,"speed":4,"cooldown":2,"behavior":"straight","target":"air","legacy":10,"art":{"asset":51,"logical_width":8,"logical_height":8,"frames":6}},
|
|
{"id":"base:projectile_ground_bomb","name":"Ground Bomb","damage":50,"speed":1,"cooldown":30,"behavior":"straight","target":"ground","legacy":9,"art":{"asset":52,"logical_width":8,"logical_height":8}},
|
|
{"id":"base:projectile_mega_bomb","name":"Mega Bomb","damage":50,"speed":2,"cooldown":60,"behavior":"straight","target":"all","legacy":11,"art":{"asset":53,"logical_width":8,"logical_height":8,"frames":4}},
|
|
{"id":"base:hostile","name":"Hostile","damage":2,"speed":2,"cooldown":1,"behavior":"straight","target":"all"}
|
|
],"enemies":[
|
|
{"id":"base:ground","name":"Ground Target","health":30,"ground":true},
|
|
{"id":"base:air","name":"Air Target","health":64,"speed":0,"path":[[0,200]],"weapon":"base:hostile"}
|
|
]})).unwrap(), "base").unwrap();
|
|
let air = registry
|
|
.enemies
|
|
.get_mut(&ContentId::from("base:air"))
|
|
.unwrap();
|
|
let mut fields = vec![0; 26];
|
|
fields[5] = 1; // Native suck susceptibility.
|
|
fields[7] = 1;
|
|
fields[9] = 1;
|
|
fields[13] = 64;
|
|
fields[16] = 1;
|
|
fields[21] = 3;
|
|
fields[22] = 1;
|
|
air.legacy = Some(raptor_replica::assets::LegacyEnemy {
|
|
name: "Synthetic Susceptible Air Target".into(),
|
|
fields,
|
|
shoot_type: vec![1; 24],
|
|
engine_x: vec![0; 24],
|
|
engine_y: vec![0; 24],
|
|
engine_width: vec![0; 24],
|
|
gun_x: vec![16; 24],
|
|
gun_y: vec![16; 24],
|
|
flight_x: vec![0; 32],
|
|
flight_y: vec![0; 32],
|
|
});
|
|
registry
|
|
}
|
|
|
|
fn inject_legacy_shot(
|
|
world: &mut World,
|
|
id: &str,
|
|
x: i32,
|
|
y: i32,
|
|
done: bool,
|
|
registry: &Registry,
|
|
) -> u64 {
|
|
let weapon = ®istry.weapons[&ContentId::from(id)];
|
|
let art = weapon.art.as_ref().unwrap();
|
|
let mut movement = LineMove::new(
|
|
x + art.logical_width as i32 / 2,
|
|
y + art.logical_height as i32 / 2,
|
|
x,
|
|
0,
|
|
);
|
|
movement.done = done;
|
|
let entity = world.next_entity;
|
|
world.next_entity += 1;
|
|
world.projectiles.push(Projectile {
|
|
entity,
|
|
weapon: id.into(),
|
|
x,
|
|
y,
|
|
px: x,
|
|
py: y,
|
|
vx: 0,
|
|
vy: 0,
|
|
hostile: false,
|
|
age: 0,
|
|
legacy_enemy: None,
|
|
legacy: Some(LegacyProjectile {
|
|
movement,
|
|
speed: weapon.speed,
|
|
frame: 0,
|
|
art_frame: 0,
|
|
delay: false,
|
|
hit: false,
|
|
start: [world.player.x + 16, world.player.y + 16],
|
|
}),
|
|
});
|
|
entity
|
|
}
|
|
|
|
#[test]
|
|
fn energy_grab_hits_air_by_one_and_suppresses_fire_after_resistance_is_exhausted() {
|
|
let registry = collision_fixture();
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
world
|
|
.spawn(&"base:ground".into(), 64, 64, ®istry)
|
|
.unwrap();
|
|
let entity = world.spawn(&"base:air".into(), 64, 64, ®istry).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
for (expected_health, expected_resistance, disabled) in
|
|
[(63, 1, false), (62, -2, false), (61, -2, true)]
|
|
{
|
|
inject_legacy_shot(
|
|
&mut world,
|
|
"base:projectile_energy_grab",
|
|
80,
|
|
80,
|
|
false,
|
|
®istry,
|
|
);
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
let air = world.enemies.iter().find(|e| e.entity == entity).unwrap();
|
|
assert_eq!(air.health, expected_health);
|
|
assert_eq!(air.energy_resistance, expected_resistance);
|
|
assert_eq!(air.shoot_disabled, disabled);
|
|
assert_eq!(world.enemies[0].health, 30); // Ground target overlaps the shot.
|
|
}
|
|
assert!(world.sounds.contains(&134)); // FX_EGRAB.
|
|
let hostile = world.projectiles.iter().filter(|p| p.hostile).count();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(
|
|
world.projectiles.iter().filter(|p| p.hostile).count(),
|
|
hostile
|
|
);
|
|
assert!(
|
|
world
|
|
.enemies
|
|
.iter()
|
|
.find(|e| e.entity == entity)
|
|
.unwrap()
|
|
.shoot_disabled
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn energy_grab_does_not_disable_an_immune_air_enemy() {
|
|
let mut registry = collision_fixture();
|
|
registry
|
|
.enemies
|
|
.get_mut(&"base:air".into())
|
|
.unwrap()
|
|
.legacy
|
|
.as_mut()
|
|
.unwrap()
|
|
.fields[5] = 0;
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
world.spawn(&"base:air".into(), 64, 64, ®istry).unwrap();
|
|
let mut scripts = ScriptRuntime::new();
|
|
for _ in 0..5 {
|
|
inject_legacy_shot(
|
|
&mut world,
|
|
"base:projectile_energy_grab",
|
|
80,
|
|
80,
|
|
false,
|
|
®istry,
|
|
);
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
}
|
|
assert_eq!(world.enemies[0].health, 59);
|
|
assert_eq!(world.enemies[0].energy_resistance, 4);
|
|
assert!(!world.enemies[0].shoot_disabled);
|
|
}
|
|
|
|
fn terrain_fixture() -> Registry {
|
|
let mut registry = collision_fixture();
|
|
let pack = serde_json::from_value(json!({"missions":[{
|
|
"id":"base:sector_1_wave_1","name":"Terrain Fixture","duration":1000,"auto_complete":false,"legacy_map":123,"columns":3,"layout":vec![0;30],
|
|
"tiles":[
|
|
{"art":{"asset":60,"logical_width":32,"logical_height":32},"health":200,"destroyed":{"asset":61,"logical_width":32,"logical_height":32}},
|
|
{"art":{"asset":62,"logical_width":32,"logical_height":32},"health":200}
|
|
]
|
|
}],"overrides":["base:sector_1_wave_1"]})).unwrap();
|
|
registry.apply(pack, "base").unwrap();
|
|
registry
|
|
}
|
|
|
|
#[test]
|
|
fn ground_bomb_damages_tile_neighbors_and_overlapping_ground_enemy_together() {
|
|
let registry = terrain_fixture();
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
world
|
|
.spawn(&"base:ground".into(), 48, 73, ®istry)
|
|
.unwrap();
|
|
let shot = inject_legacy_shot(
|
|
&mut world,
|
|
"base:projectile_ground_bomb",
|
|
64,
|
|
89,
|
|
false,
|
|
®istry,
|
|
);
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.enemies[0].health, 25);
|
|
assert!(!world.projectiles.iter().any(|p| p.entity == shot));
|
|
for position in [19, 18, 16, 20] {
|
|
assert_eq!(
|
|
world.terrain_health[position], 150,
|
|
"primary neighbor {position}"
|
|
);
|
|
}
|
|
for position in [15, 13, 17] {
|
|
assert_eq!(
|
|
world.terrain_health[position], 175,
|
|
"upper neighbor {position}"
|
|
);
|
|
}
|
|
assert_eq!(world.terrain_health[22], 200);
|
|
assert_eq!(world.terrain_health[21], 200);
|
|
}
|
|
|
|
#[test]
|
|
fn mega_bomb_uses_visible_destructible_tiles_at_fixed_twenty_damage_and_clears_shots() {
|
|
let mut registry = terrain_fixture();
|
|
registry
|
|
.missions
|
|
.get_mut(&Pilot::default().mission)
|
|
.unwrap()
|
|
.layout[8] = 1;
|
|
let mut world = World::new(&Pilot::default().mission, &Pilot::default(), ®istry).unwrap();
|
|
world.terrain_dead[7] = true;
|
|
world
|
|
.spawn(&"base:ground".into(), 64, 64, ®istry)
|
|
.unwrap();
|
|
let hostile = world.next_entity;
|
|
world.next_entity += 1;
|
|
world.projectiles.push(Projectile {
|
|
entity: hostile,
|
|
weapon: "base:hostile".into(),
|
|
x: 100,
|
|
y: 100,
|
|
px: 100,
|
|
py: 100,
|
|
vx: 0,
|
|
vy: 1,
|
|
hostile: true,
|
|
age: 0,
|
|
legacy: None,
|
|
legacy_enemy: None,
|
|
});
|
|
inject_legacy_shot(
|
|
&mut world,
|
|
"base:projectile_mega_bomb",
|
|
100,
|
|
100,
|
|
true,
|
|
®istry,
|
|
);
|
|
let mut scripts = ScriptRuntime::new();
|
|
world
|
|
.step(Input::default(), ®istry, &mut scripts)
|
|
.unwrap();
|
|
assert_eq!(world.enemies[0].health, -20); // Original enemy damage50, separate from tiles.
|
|
assert!(world.projectiles.is_empty());
|
|
for position in 0..30 {
|
|
let expected = if position < 6 || position == 7 || position == 8 {
|
|
200
|
|
} else {
|
|
180
|
|
};
|
|
assert_eq!(
|
|
world.terrain_health[position], expected,
|
|
"terrain position{position}"
|
|
);
|
|
}
|
|
}
|