Some checks are pending
CI / checks (push) Waiting to run
Из ветки feat/swep (a46e4d8,1fe36b6,5a8f971): - выбор SWEP из list.Get('Weapon') (фильтр Spawnable + существующая ViewModel), слоты 1..9, SOURCE_RS_SWEP для старта, Holster/Deploy при смене, вьюмодель из SWEP.ViewModel (SOURCE_RS_VIEW_MODEL — override); - ЛКМ → PrimaryAttack через call_weapon_method, автоматический/одиночный огонь, Think каждый кадр; без SWEP — прежние физган и fire-анимация; - HUD: PrintName и патроны Clip1/ClipSize; - заглушки GMod-API под реальные скрипты: CurTime, IsValid, NULL, player.GetAll, util.TraceLine/TraceHull/TraceEntity/TracePhysics, trace.*, sound.*/EmitSound, константы PLAYER_ATTACK*/ACT_VM_*/MASK_*, база SWEP (GetOwner/GetViewModel/ NetworkVar/SetNextPrimaryFire/SetHoldType/Clip1/EmitSound/SendWeaponAnim/ SequenceDuration/ShootEffects/Remove), game.SinglePlayer как функция, GetConVar для sv_defaultdeployspeed/phys_pushscale; - тест real_gmod_weapon_methods_run_without_errors прогоняет настоящие weapon_fists/flechettegun/medkit (SetupDataTables..Holster) без ошибок Lua. Конфликт в crates/lua/tests/scripts.rs разрешён: сохранены тесты include и геймода из feat/lua-pre и добавлен тест SWEP из feat/swep. Проверено: 27 сьютов, 46+4+4 теста glua зелёные.
198 lines
8.2 KiB
Rust
198 lines
8.2 KiB
Rust
//! Загрузка настоящих скриптов GMod (оружие и энтити) из установки игры.
|
||
//!
|
||
//! Без ассетов тесты пропускаются: путь задаётся `SOURCE_RS_ASSETS` или
|
||
//! `~/gmod_server/server/garrysmod`.
|
||
|
||
use glua::LuaRuntime;
|
||
use std::path::PathBuf;
|
||
|
||
fn game_dir() -> Option<PathBuf> {
|
||
let root = std::env::var_os("SOURCE_RS_ASSETS")
|
||
.map(PathBuf::from)
|
||
.or_else(|| {
|
||
std::env::var_os("HOME").map(|home| PathBuf::from(home).join("gmod_server/server"))
|
||
})?;
|
||
let game = root.join("garrysmod");
|
||
game.is_dir().then_some(game)
|
||
}
|
||
|
||
#[test]
|
||
fn real_gmod_weapons_and_entities_load() {
|
||
let Some(game) = game_dir() else {
|
||
eprintln!("пропуск: ассеты Garry's Mod не найдены");
|
||
return;
|
||
};
|
||
let runtime = LuaRuntime::new().expect("рантайм");
|
||
let (weapons, entities, failed) = runtime.load_scripts(&game);
|
||
eprintln!("скрипты: оружия {weapons}, энтити {entities}, ошибок {failed}");
|
||
assert!(weapons >= 3, "мало оружия: {weapons}");
|
||
// `widget_bones.lua` использует GMod-расширение `continue`; после
|
||
// препроцессора грузятся все 6 энтити.
|
||
assert!(entities >= 6, "мало энтити: {entities}");
|
||
assert_eq!(failed, 0, "ошибки загрузки скриптов");
|
||
|
||
// Поля SWEP прочитаны (Primary создаётся до выполнения скрипта).
|
||
let clip = runtime
|
||
.probe("return tostring(weapons.Get('weapon_fists').Primary.ClipSize)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(clip, "-1", "Primary.ClipSize кулаков");
|
||
let model = runtime
|
||
.probe("return weapons.Get('weapon_fists').ViewModel.path")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert!(model.contains("c_arms"), "ViewModel: {model}");
|
||
|
||
// Реестр list.Get("Weapon") — то, из чего строится меню.
|
||
let count = runtime
|
||
.probe("local n=0 for _ in pairs(list.Get('Weapon') or {}) do n=n+1 end return tostring(n)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
eprintln!("list.Get('Weapon') = {count}");
|
||
assert!(
|
||
count.parse::<usize>().unwrap_or(0) >= 3,
|
||
"list.Get('Weapon'): {count}"
|
||
);
|
||
|
||
// Энтити: запись доступна и помечена как спавнабельная.
|
||
let entity = runtime
|
||
.probe("return tostring(scripted_ents.Get('sent_ball') ~= nil)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(entity, "true", "scripted_ents.Get('sent_ball')");
|
||
}
|
||
|
||
#[test]
|
||
fn real_gmod_include_loads_lua_includes() {
|
||
let Some(game) = game_dir() else {
|
||
eprintln!("пропуск: ассеты Garry's Mod не найдены");
|
||
return;
|
||
};
|
||
let runtime = LuaRuntime::new().expect("рантайм");
|
||
runtime.set_file_root(&game).expect("корень игры");
|
||
// Реальный файл из lua/includes: расширение метатаблицы Vector.
|
||
runtime
|
||
.run(
|
||
"includes/extensions/vector.lua",
|
||
br#"
|
||
include( "includes/extensions/vector.lua" )
|
||
include( "includes/extensions/angle.lua" )
|
||
"#,
|
||
)
|
||
.expect("lua/includes загрузились");
|
||
let to_color = runtime
|
||
.probe("return type(FindMetaTable('Vector').ToColor)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(to_color, "function", "Vector:ToColor из includes");
|
||
let snap = runtime
|
||
.probe("return type(FindMetaTable('Angle').SnapTo)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(snap, "function", "Angle:SnapTo из includes");
|
||
}
|
||
|
||
#[test]
|
||
fn real_gmod_sandbox_gamemode_loads_without_breaking_registries() {
|
||
let Some(game) = game_dir() else {
|
||
eprintln!("пропуск: ассеты Garry's Mod не найдены");
|
||
return;
|
||
};
|
||
let runtime = LuaRuntime::new().expect("рантайм");
|
||
runtime.set_file_root(&game).expect("корень игры");
|
||
let (_, entities, failed) = runtime.load_scripts(&game);
|
||
assert_eq!(
|
||
failed, 0,
|
||
"скрипты оружия/энтити должны грузиться без ошибок"
|
||
);
|
||
|
||
let before = runtime
|
||
.probe("local n=0 for _ in pairs(list.Get('SpawnableEntities') or {}) do n=n+1 end return tostring(n)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert!(
|
||
before.parse::<usize>().unwrap_or(0) >= entities,
|
||
"SpawnableEntities до геймода: {before}"
|
||
);
|
||
|
||
let report = runtime.load_gamemode(&game, "sandbox");
|
||
eprintln!("геймод sandbox: загружено {:?}", report.loaded);
|
||
for error in &report.errors {
|
||
eprintln!("геймод sandbox: ошибка: {error}");
|
||
}
|
||
assert!(
|
||
report.loaded.iter().any(|file| file == "init.lua"),
|
||
"init.lua Sandbox должен выполниться"
|
||
);
|
||
// cl_init.lua тянет VGUI/Derma: ошибка ожидаема, но загрузчик обязан
|
||
// попытаться выполнить файл и собрать ошибку, а не упасть.
|
||
assert!(
|
||
report
|
||
.errors
|
||
.iter()
|
||
.any(|error| error.starts_with("cl_init.lua:")),
|
||
"ошибка cl_init.lua должна быть собрана: {:?}",
|
||
report.errors
|
||
);
|
||
|
||
// Реестры не должны пострадать от загрузки геймода: именно из них
|
||
// строятся меню спавна и выдача оружия.
|
||
let after = runtime
|
||
.probe("local n=0 for _ in pairs(list.Get('SpawnableEntities') or {}) do n=n+1 end return tostring(n)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(after, before, "SpawnableEntities после геймода");
|
||
let ball = runtime
|
||
.probe("return tostring(scripted_ents.Get('sent_ball') ~= nil)")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(ball, "true", "sent_ball после геймода");
|
||
let gm_name = runtime
|
||
.probe("return GAMEMODE and GAMEMODE.Name or ''")
|
||
.expect("probe")
|
||
.unwrap_or_default();
|
||
assert_eq!(gm_name, "Sandbox", "GAMEMODE.Name из shared.lua");
|
||
}
|
||
|
||
/// Реальные SWEP-скрипты вызываются клиентом: Deploy, атака, Think, Holster.
|
||
///
|
||
/// Тест важен тем, что не мокает GMod-API, а прогоняет **настоящие** файлы
|
||
/// оружия из установки: любая отсутствующая функция (`CurTime`, `IsValid`,
|
||
/// `util.TraceLine`, `GetViewModel`, ...) здесь станет ошибкой Lua.
|
||
#[test]
|
||
fn real_gmod_weapon_methods_run_without_errors() {
|
||
let Some(game) = game_dir() else {
|
||
eprintln!("пропуск: ассеты Garry's Mod не найдены");
|
||
return;
|
||
};
|
||
let runtime = LuaRuntime::new().expect("рантайм");
|
||
let (weapons, _, _) = runtime.load_scripts(&game);
|
||
assert!(weapons >= 3, "мало оружия: {weapons}");
|
||
|
||
for class in ["weapon_fists", "weapon_flechettegun", "weapon_medkit"] {
|
||
for method in [
|
||
"SetupDataTables",
|
||
"Initialize",
|
||
"Deploy",
|
||
"PrimaryAttack",
|
||
"Think",
|
||
"SecondaryAttack",
|
||
"Holster",
|
||
] {
|
||
runtime
|
||
.call_weapon_method(class, method)
|
||
.unwrap_or_else(|err| panic!("{class}:{method}: {err}"));
|
||
}
|
||
}
|
||
|
||
// Магазин медика: после атаки по пустой трассе он не тратится, но
|
||
// значение обязано читаться как число (общий контракт Clip1).
|
||
let clip = runtime
|
||
.call_weapon_number("weapon_medkit", "Clip1")
|
||
.expect("Clip1 медика");
|
||
assert!(clip >= 0.0, "Clip1: {clip}");
|
||
let clip_size = runtime
|
||
.weapon_number("weapon_medkit", "Primary.ClipSize")
|
||
.expect("Primary.ClipSize");
|
||
assert_eq!(clip_size, 100.0);
|
||
}
|