gmod-rs/crates/lua/tests/scripts.rs
Niko Marmeladkov a46e4d827d Lua: база SWEP и GMod-заглушки для оружия
- 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.
2026-10-24 13:29:35 +03:00

105 lines
4.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Загрузка настоящих скриптов 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);
}