mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 02:56:17 -10:00
Merge pull request #7690 from levzzz5154/modapi/entity-moves
Native mods: teleport, rotation, head yaw, facing, size and pushes through EntityRef (#7387)
This commit is contained in:
10 files changed
+411
-4
No files matched your search
@@ -241,6 +241,7 @@ mod native_diagnostics;
|
||||
mod native_entities;
|
||||
mod native_entity_data;
|
||||
mod native_entity_motion;
|
||||
mod native_entity_moves;
|
||||
mod native_entity_refs;
|
||||
mod native_entity_state;
|
||||
mod native_explosions;
|
||||
|
||||
@@ -1518,7 +1518,7 @@ impl Game<'_> {
|
||||
self.feedback(message);
|
||||
Ok(targets.len() as i32)
|
||||
}
|
||||
fn teleport_to(
|
||||
pub(in crate::game) fn teleport_to(
|
||||
&mut self,
|
||||
t: Target,
|
||||
pos: [f64; 3],
|
||||
@@ -4586,6 +4586,21 @@ mod teleport_rider_tests {
|
||||
rider2.yaw, 80.0,
|
||||
"a vehicle turned by `facing` leaves its rider"
|
||||
);
|
||||
// Native rotation preserves a rider's mount, position and motion.
|
||||
game.native_lifecycle
|
||||
.begin_open(horizon_client::mod_api::NativeAuthority::IntegratedServer);
|
||||
let rider = game.mobs.iter_mut().find(|m| m.id == 51).unwrap();
|
||||
rider.velocity = Vec3::new(0.2, 0.3, 0.4);
|
||||
let position = rider.position;
|
||||
let velocity = rider.velocity;
|
||||
let handle = game.native_entity(51).unwrap();
|
||||
assert!(game.native_entity_set_rotation(handle, -135.0, 120.0));
|
||||
let rider = game.mobs.iter().find(|m| m.id == 51).unwrap();
|
||||
assert_eq!(rider.extra.mount, Some(50));
|
||||
assert_eq!(rider.position, position);
|
||||
assert_eq!(rider.velocity, velocity);
|
||||
assert_eq!((rider.yaw, rider.head_pitch), (-135.0, 90.0));
|
||||
assert!(!game.native_entity_set_rotation(handle, f32::NAN, 0.0));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ fn corners(b: Aabb) -> [[f64; 3]; 2] {
|
||||
|
||||
impl Game<'_> {
|
||||
/// The simulated entity (one of the shared entity walk's lists) with `id`.
|
||||
fn native_walk_entity(&self, id: u32) -> Option<EntityHandle> {
|
||||
pub(super) fn native_walk_entity(&self, id: u32) -> Option<EntityHandle> {
|
||||
self.entity_handles()
|
||||
.find(|h| with_entity!(self, *h, e => e.id() == id))
|
||||
}
|
||||
@@ -27,7 +27,10 @@ impl Game<'_> {
|
||||
self.player.as_ref()
|
||||
}
|
||||
/// The server's copy of the player a target names.
|
||||
fn native_target_guest(&self, t: Target) -> Option<&horizon_client::lan::guest::Guest> {
|
||||
pub(super) fn native_target_guest(
|
||||
&self,
|
||||
t: Target,
|
||||
) -> Option<&horizon_client::lan::guest::Guest> {
|
||||
match t {
|
||||
Target::Player => self.command_guest(self.lan_acting?),
|
||||
Target::Guest(conn) => self.command_guest(conn),
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
//! Moving and turning an entity for native server mods, through an
|
||||
//! `EntityRef` (docs/native-mods.md, "Entity and level handles", #7387):
|
||||
//! teleport, rotation, head yaw, facing, size and pushes. Writes move the
|
||||
//! entity as `/tp` and `/rotate` do, so clients see it as they see those.
|
||||
//! Reads return `None`, writes false, for a stale handle or on a client.
|
||||
use super::entity::{Entity as _, EntityHandle, with_entity_mut};
|
||||
use super::{Game, commands::Target, native_mobs::spawnable};
|
||||
use horizon_client::mod_api::EntityRef;
|
||||
use horizon_core::geometry::Vec3;
|
||||
|
||||
/// `Direction.fromYRot`: the horizontal direction a yaw faces.
|
||||
fn facing(yaw: f32) -> &'static str {
|
||||
const BY_2D: [&str; 4] = ["south", "west", "north", "east"];
|
||||
BY_2D[((f64::from(yaw) / 90.0 + 0.5).floor() as i32 & 3) as usize]
|
||||
}
|
||||
|
||||
impl Game<'_> {
|
||||
/// Moves the entity to `pos` in its level (`Entity.teleportTo`), as
|
||||
/// `/tp` does: a player is placed there and told, a rider gets off. With
|
||||
/// `rotation` (yaw, pitch in degrees) it also turns. False for a position
|
||||
/// outside the world's bounds. API @API:entity_moves@.
|
||||
pub(super) fn native_entity_teleport_to(
|
||||
&mut self,
|
||||
entity: EntityRef,
|
||||
pos: [f64; 3],
|
||||
rotation: Option<[f32; 2]>,
|
||||
) -> bool {
|
||||
let Some(t) = self.native_entity_target(entity) else {
|
||||
return false;
|
||||
};
|
||||
if !spawnable(pos) || rotation.is_some_and(|r| !r.iter().all(|v| v.is_finite())) {
|
||||
return false;
|
||||
}
|
||||
self.teleport_to(t, pos, rotation, true);
|
||||
true
|
||||
}
|
||||
/// Turns the entity to `yaw` and `pitch` in degrees (`Entity.setYRot`,
|
||||
/// `setXRot`), as `/rotate` does: the pitch is clamped to -90..90, and a
|
||||
/// player's client is told. API @API:entity_moves@.
|
||||
pub(super) fn native_entity_set_rotation(
|
||||
&mut self,
|
||||
entity: EntityRef,
|
||||
yaw: f32,
|
||||
pitch: f32,
|
||||
) -> bool {
|
||||
let Some(t) = self.native_entity_target(entity) else {
|
||||
return false;
|
||||
};
|
||||
if self.target_position(t).is_none() || !yaw.is_finite() || !pitch.is_finite() {
|
||||
return false;
|
||||
}
|
||||
self.set_rotation(t, [yaw, pitch]);
|
||||
true
|
||||
}
|
||||
/// The yaw of the entity's head (`Entity.getYHeadRot`): a mob's or a
|
||||
/// player's own, the body's yaw for the kinds without a head. API
|
||||
/// @API:entity_moves@.
|
||||
pub(super) fn native_entity_head_yaw(&self, entity: EntityRef) -> Option<f32> {
|
||||
let t = self.native_entity_target(entity)?;
|
||||
if let Some(g) = self.native_target_guest(t) {
|
||||
return Some(g.head_yaw);
|
||||
}
|
||||
if let Target::Mob(id) = t {
|
||||
return Some(self.mobs.iter().find(|m| m.id == id)?.head_yaw);
|
||||
}
|
||||
Some(self.target_rotation(t)[0])
|
||||
}
|
||||
/// Turns a mob's head to `yaw` (`Entity.setYHeadRot`); its body follows
|
||||
/// as its AI turns it. False for the other kinds. API @API:entity_moves@.
|
||||
pub(super) fn native_entity_set_head_yaw(&mut self, entity: EntityRef, yaw: f32) -> bool {
|
||||
let Some(Target::Mob(id)) = self.native_entity_target(entity) else {
|
||||
return false;
|
||||
};
|
||||
match self.mobs.iter_mut().find(|m| m.id == id) {
|
||||
Some(m) if yaw.is_finite() => {
|
||||
m.head_yaw = yaw;
|
||||
m.needs_sync = true;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
/// The horizontal direction the entity faces (`Entity.getDirection`):
|
||||
/// `north`, `south`, `east` or `west`. API @API:entity_moves@.
|
||||
pub(super) fn native_entity_direction(&self, entity: EntityRef) -> Option<&'static str> {
|
||||
let t = self.native_entity_target(entity)?;
|
||||
Some(facing(self.target_rotation(t)[0]))
|
||||
}
|
||||
/// The entity's width and height (`Entity.getBbWidth`, `getBbHeight`):
|
||||
/// its box's, so a baby's or a scaled entity's own. API
|
||||
/// @API:entity_moves@.
|
||||
pub(super) fn native_entity_dimensions(&self, entity: EntityRef) -> Option<[f64; 2]> {
|
||||
let [min, max] = self.native_entity_bounding_box(entity)?;
|
||||
Some([max[0] - min[0], max[1] - min[1]])
|
||||
}
|
||||
/// Adds `delta` to the entity's velocity (`Entity.push`,
|
||||
/// `addDeltaMovement`). False for a player (its client moves it), an
|
||||
/// entity Horizon doesn't simulate, or a value that isn't finite. API
|
||||
/// @API:entity_moves@.
|
||||
pub(super) fn native_entity_push(&mut self, entity: EntityRef, delta: [f64; 3]) -> bool {
|
||||
if !delta.iter().all(|v| v.is_finite()) || self.native_entity_target(entity).is_none() {
|
||||
return false;
|
||||
}
|
||||
let Some(h) = self.native_walk_entity(entity.id()) else {
|
||||
return false;
|
||||
};
|
||||
let [x, y, z] = delta;
|
||||
with_entity_mut!(self, h, e => {
|
||||
let v = e.velocity_mut();
|
||||
*v = Vec3::new(v.x + x, v.y + y, v.z + z);
|
||||
});
|
||||
if let EntityHandle::Mob(i) = h {
|
||||
self.mobs[i].needs_sync = true;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn a_yaw_faces_vanillas_direction() {
|
||||
assert_eq!(super::facing(0.0), "south");
|
||||
assert_eq!(super::facing(90.0), "west");
|
||||
assert_eq!(super::facing(180.0), "north");
|
||||
assert_eq!(super::facing(-90.0), "east");
|
||||
assert_eq!(super::facing(-45.0), "south");
|
||||
assert_eq!(super::facing(-46.0), "east");
|
||||
assert_eq!(super::facing(720.0 + 170.0), "north");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positions_stay_inside_the_worlds_bounds() {
|
||||
assert!(super::spawnable([29_999_999.0, -64.0, 0.0]));
|
||||
assert!(!super::spawnable([30_000_000.0, 64.0, 0.0]));
|
||||
assert!(!super::spawnable([0.0, 20_000_001.0, 0.0]));
|
||||
assert!(!super::spawnable([f64::NAN, 64.0, 0.0]));
|
||||
}
|
||||
/// Independent vanilla outcomes for spawn bounds and the yaw-to-direction conversion.
|
||||
#[test]
|
||||
fn move_boundaries_match_vanilla() {
|
||||
use serde_json::Value;
|
||||
let cases = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../tests/fixtures/native_entity_moves.jsonl"
|
||||
));
|
||||
let expected = include_str!(concat!(
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
"/../../tests/fixtures/native_entity_moves_expected.jsonl"
|
||||
));
|
||||
let rows = |text: &str| {
|
||||
text.lines()
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(|line| serde_json::from_str::<Value>(line).unwrap())
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let (cases, expected) = (rows(cases), rows(expected));
|
||||
assert_eq!(cases.len(), expected.len());
|
||||
assert!(cases.len() >= 20);
|
||||
for (input, expected) in cases.iter().zip(&expected) {
|
||||
assert_eq!(input["name"], expected["name"]);
|
||||
let pos = std::array::from_fn(|i| input["pos"][i].as_f64().unwrap());
|
||||
let yaw = input["yaw"].as_f64().unwrap() as f32;
|
||||
assert_eq!(
|
||||
super::spawnable(pos),
|
||||
expected["spawnable"].as_bool().unwrap(),
|
||||
"{}",
|
||||
input["name"]
|
||||
);
|
||||
assert_eq!(
|
||||
super::facing(yaw),
|
||||
expected["direction"].as_str().unwrap(),
|
||||
"{}",
|
||||
input["name"]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ fn finite(v: [f64; 3]) -> bool {
|
||||
}
|
||||
|
||||
/// `/summon`'s world bounds (`Level.isInSpawnableBounds`).
|
||||
fn spawnable(pos: [f64; 3]) -> bool {
|
||||
pub(super) fn spawnable(pos: [f64; 3]) -> bool {
|
||||
finite(pos)
|
||||
&& (-30_000_000.0..30_000_000.0).contains(&pos[0])
|
||||
&& (-30_000_000.0..30_000_000.0).contains(&pos[2])
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package dev.horizonlab.parity;
|
||||
|
||||
import com.google.gson.*;
|
||||
import java.nio.file.*;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
|
||||
/** Vanilla bounds and facing used by the native EntityRef movement methods. */
|
||||
public final class VanillaNativeEntityMovesOracle {
|
||||
public static void main(String[] args) throws Exception {
|
||||
ParityData.init();
|
||||
try (var output = Files.newBufferedWriter(Path.of(args[1]))) {
|
||||
for (String line : Files.readAllLines(Path.of(args[0]))) {
|
||||
if (line.isBlank()) continue;
|
||||
var input = JsonParser.parseString(line).getAsJsonObject();
|
||||
var pos = input.getAsJsonArray("pos");
|
||||
var point = BlockPos.containing(pos.get(0).getAsDouble(), pos.get(1).getAsDouble(), pos.get(2).getAsDouble());
|
||||
var result = new JsonObject();
|
||||
result.add("name", input.get("name"));
|
||||
result.addProperty("spawnable", Level.isInSpawnableBounds(point));
|
||||
// Entity.getDirection widens the entity's float yaw to Direction's double input.
|
||||
result.addProperty("direction", Direction.fromYRot((double) input.get("yaw").getAsFloat()).getSerializedName());
|
||||
output.write(result.toString());
|
||||
output.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{"name":"origin","pos":[0,64,0],"yaw":0}
|
||||
{"name":"minimum-x","pos":[-30000000,64,0],"yaw":0}
|
||||
{"name":"minimum-z","pos":[0,64,-30000000],"yaw":0}
|
||||
{"name":"minimum-y","pos":[0,-20000000,0],"yaw":0}
|
||||
{"name":"below-x","pos":[-30000000.01,64,0],"yaw":0}
|
||||
{"name":"below-z","pos":[0,64,-30000000.01],"yaw":0}
|
||||
{"name":"below-y","pos":[0,-20000000.01,0],"yaw":0}
|
||||
{"name":"maximum-x","pos":[30000000,64,0],"yaw":0}
|
||||
{"name":"maximum-z","pos":[0,64,30000000],"yaw":0}
|
||||
{"name":"maximum-y","pos":[0,20000000,0],"yaw":0}
|
||||
{"name":"inside-positive","pos":[29999999.99,19999999.99,29999999.99],"yaw":0}
|
||||
{"name":"yaw-0","pos":[0,64,0],"yaw":0}
|
||||
{"name":"yaw-45","pos":[0,64,0],"yaw":45}
|
||||
{"name":"yaw-90","pos":[0,64,0],"yaw":90}
|
||||
{"name":"yaw-135","pos":[0,64,0],"yaw":135}
|
||||
{"name":"yaw-180","pos":[0,64,0],"yaw":180}
|
||||
{"name":"yaw-225","pos":[0,64,0],"yaw":225}
|
||||
{"name":"yaw-270","pos":[0,64,0],"yaw":270}
|
||||
{"name":"yaw--45","pos":[0,64,0],"yaw":-45}
|
||||
{"name":"yaw--46","pos":[0,64,0],"yaw":-46}
|
||||
{"name":"yaw--90","pos":[0,64,0],"yaw":-90}
|
||||
{"name":"yaw-890","pos":[0,64,0],"yaw":890}
|
||||
{"name":"yaw-1000000000000.0","pos":[0,64,0],"yaw":1000000000000.0}
|
||||
{"name":"yaw--1000000000000.0","pos":[0,64,0],"yaw":-1000000000000.0}
|
||||
{"name":"yaw-3.4028234663852886e+38","pos":[0,64,0],"yaw":3.4028234663852886e+38}
|
||||
{"name":"yaw--3.4028234663852886e+38","pos":[0,64,0],"yaw":-3.4028234663852886e+38}
|
||||
@@ -0,0 +1,26 @@
|
||||
{"name":"origin","spawnable":true,"direction":"south"}
|
||||
{"name":"minimum-x","spawnable":true,"direction":"south"}
|
||||
{"name":"minimum-z","spawnable":true,"direction":"south"}
|
||||
{"name":"minimum-y","spawnable":true,"direction":"south"}
|
||||
{"name":"below-x","spawnable":false,"direction":"south"}
|
||||
{"name":"below-z","spawnable":false,"direction":"south"}
|
||||
{"name":"below-y","spawnable":false,"direction":"south"}
|
||||
{"name":"maximum-x","spawnable":false,"direction":"south"}
|
||||
{"name":"maximum-z","spawnable":false,"direction":"south"}
|
||||
{"name":"maximum-y","spawnable":false,"direction":"south"}
|
||||
{"name":"inside-positive","spawnable":true,"direction":"south"}
|
||||
{"name":"yaw-0","spawnable":true,"direction":"south"}
|
||||
{"name":"yaw-45","spawnable":true,"direction":"west"}
|
||||
{"name":"yaw-90","spawnable":true,"direction":"west"}
|
||||
{"name":"yaw-135","spawnable":true,"direction":"north"}
|
||||
{"name":"yaw-180","spawnable":true,"direction":"north"}
|
||||
{"name":"yaw-225","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw-270","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw--45","spawnable":true,"direction":"south"}
|
||||
{"name":"yaw--46","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw--90","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw-890","spawnable":true,"direction":"north"}
|
||||
{"name":"yaw-1000000000000.0","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw--1000000000000.0","spawnable":true,"direction":"south"}
|
||||
{"name":"yaw-3.4028234663852886e+38","spawnable":true,"direction":"east"}
|
||||
{"name":"yaw--3.4028234663852886e+38","spawnable":true,"direction":"south"}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"hooks": [],
|
||||
"helpers": [
|
||||
{
|
||||
"name": "entity_teleport_to",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_teleport_to",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_set_rotation",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_set_rotation",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_head_yaw",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_head_yaw",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_set_head_yaw",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_set_head_yaw",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_direction",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_direction",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_dimensions",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_dimensions",
|
||||
"receiver": "entity"
|
||||
},
|
||||
{
|
||||
"name": "entity_push",
|
||||
"target": "horizon_client::game::native_entity_moves::Game<'_>::native_entity_push",
|
||||
"receiver": "entity"
|
||||
}
|
||||
],
|
||||
"rust_summary": "moving and turning an entity through an `EntityRef`: teleport, rotation, head yaw, facing, size and pushes.",
|
||||
"doc_summary": "`entity.teleport_to(host, pos, rotation)` and `set_rotation(host, yaw, pitch)` (as `/tp` and `/rotate` move and turn it), `head_yaw`, `set_head_yaw`, `direction` (`north`...), `dimensions` (width, height) and `push(host, delta)`. Bundles need \"hooks\": @API:entity_moves@.",
|
||||
"comments": [
|
||||
"crates/horizon-client/src/game/native_entity_moves.rs"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check native entity movement bounds/facing against both pinned vanilla jars."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
try:
|
||||
from . import lab, oracle_harness as harness, test_rust_parity as base, integration_env
|
||||
except ImportError:
|
||||
import lab, oracle_harness as harness, test_rust_parity as base, integration_env
|
||||
|
||||
|
||||
def rows(path):
|
||||
return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]
|
||||
|
||||
|
||||
def main():
|
||||
lock, jars, libraries = base.verify_artifacts()
|
||||
build = lab.LOCAL / 'native-entity-moves'
|
||||
build.mkdir(parents=True, exist_ok=True)
|
||||
report = build / 'report.json'
|
||||
report.unlink(missing_ok=True)
|
||||
fixture = lab.ROOT / 'tests/fixtures/native_entity_moves.jsonl'
|
||||
golden = fixture.with_name('native_entity_moves_expected.jsonl')
|
||||
sources = [fixture, golden, Path(__file__).resolve(), lab.ROOT / 'Cargo.toml',
|
||||
lab.ROOT / 'Cargo.lock', lab.ROOT / 'tools/oracle_harness.py',
|
||||
lab.ROOT / 'tools/test_rust_parity.py', lab.ROOT / 'tools/lab.py',
|
||||
lab.ROOT / 'tools/integration_env.py',
|
||||
lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaNativeEntityMovesOracle.java',
|
||||
lab.ROOT / 'mods/horizon/src/test/java/dev/horizonlab/parity/ParityData.java']
|
||||
sources += sorted((lab.ROOT / 'crates').rglob('*.rs'))
|
||||
hashes = base.source_hashes(sources)
|
||||
expected = rows(golden)
|
||||
assert len(expected) == len(rows(fixture))
|
||||
classes, _, _ = harness.compile_oracle(build, 'VanillaNativeEntityMovesOracle',
|
||||
jars, libraries, lock['minecraft']['java_major'])
|
||||
for side in ('server', 'client'):
|
||||
output = build / (side + '-expected.jsonl')
|
||||
dependencies = os.pathsep.join(map(str, [jars[side], *libraries]))
|
||||
harness.java_run(build, classes, dependencies, 'VanillaNativeEntityMovesOracle',
|
||||
[fixture, output], side)
|
||||
assert rows(output) == expected, f'{side}: committed outcomes differ from vanilla'
|
||||
harness.run_logged(['cargo', 'test', '--locked', '-p', 'horizon-client', '--lib', '--no-run', '--message-format=json'],
|
||||
build, 'compile-rust', timeout=3600)
|
||||
artifacts = [json.loads(line) for line in (build / 'compile-rust.log').read_text().splitlines()
|
||||
if line.startswith('{')]
|
||||
executables = {record['executable'] for record in artifacts
|
||||
if record.get('reason') == 'compiler-artifact' and record.get('executable')
|
||||
and record.get('profile', {}).get('test')
|
||||
and record['target']['name'] == 'horizon_client'
|
||||
and 'lib' in record['target']['kind']}
|
||||
assert len(executables) == 1, executables
|
||||
executable = executables.pop()
|
||||
with tempfile.TemporaryDirectory(prefix='horizon-move-properties-') as scratch:
|
||||
env = integration_env.game_environment(os.environ, scratch)
|
||||
for key in ('XDG_CACHE_HOME', 'TMPDIR', 'HORIZON_LAUNCHER_TRASH',
|
||||
'HORIZON_LAUNCHER_CLIPBOARD', 'HORIZON_LAUNCHER_DESKTOP'):
|
||||
directory = Path(scratch) / key
|
||||
directory.mkdir()
|
||||
env[key] = str(directory)
|
||||
with (build / 'rust.log').open('w') as output:
|
||||
subprocess.run([executable,
|
||||
'game::native_entity_moves::tests::move_boundaries_match_vanilla',
|
||||
'--exact', '--test-threads=1', '--nocapture'], cwd=scratch, env=env,
|
||||
stdout=output, stderr=subprocess.STDOUT, check=True, timeout=900)
|
||||
text = (build / 'rust.log').read_text()
|
||||
assert re.search(r'^test result: ok\. 1 passed;', text, re.MULTILINE), text
|
||||
base.ensure_sources_unchanged(sources, hashes)
|
||||
lab.write_json(report, dict(schema=1, minecraft=lock['minecraft']['version'],
|
||||
sources_sha256=hashes, cases=len(expected),
|
||||
input_sha256=lab.digest(fixture, 'sha256'),
|
||||
expected_sha256=lab.digest(golden, 'sha256'),
|
||||
jars_sha256={side: lab.digest(jars[side], 'sha256') for side in ('client', 'server')},
|
||||
rust_test_executable_sha256=lab.digest(Path(executable), 'sha256'),
|
||||
scope='Native entity spawn-bound and facing calculations, compared with Level and Direction. '
|
||||
'Does not exercise teleport networking, rider state or a game tick loop.'))
|
||||
print(f'{len(expected)} native entity movement cases match both vanilla jars: {report}', flush=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in new issue
Block a user