- swep_base.lua: GetOwner/GetViewModel, NetworkVar, SetNextPrimaryFire, Clip1, SetHoldType, EmitSound и безопасные заглушки; - глобальные CurTime, IsValid, player.GetAll (таблица player), util.TraceLine/TraceHull, trace.*, sound.*, константы CLIENT/SERVER; - game.SinglePlayer стал функцией (как в GMod), GetConVar знает sv_defaultdeployspeed/phys_pushscale — ноль давал inf в Deploy; - загрузчик SWEP даёт базовые методы через метатаблицу, инициализирует SetupDataTables/Initialize и стартовый магазин; - модель без геометрии (c_arms) теперь логирует причину, а не молчит; - тест прогоняет реальные Deploy/PrimaryAttack/Think/Holster трёх SWEP.
105 lines
4.5 KiB
Rust
105 lines
4.5 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}");
|
||
// 5 из 6: `widget_bones.lua` использует GMod-расширение `continue`,
|
||
// которого нет в стандартном Lua (отдельная задача препроцессора).
|
||
assert!(entities >= 5, "мало энтити: {entities}");
|
||
|
||
// Поля 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')");
|
||
}
|
||
|
||
/// Реальные 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);
|
||
}
|