From 4938da4e4c03fd61d3e8ac855246da6363448a56 Mon Sep 17 00:00:00 2001 From: Roman Chistokhodov Date: Mon, 18 Nov 2024 20:28:01 +0300 Subject: [PATCH] Add tests. Run them in CI. Make soundscripts use fixed_vector instead of std::array --- .github/workflows/build.yml | 12 + cl_dll/CMakeLists.txt | 3 + dlls/CMakeLists.txt | 5 + dlls/agrunt.cpp | 6 +- dlls/ammoregistry.cpp | 26 +- dlls/cbase.cpp | 2 +- dlls/cdll_dll.h | 1 - dlls/effects.cpp | 40 +-- dlls/game.cpp | 39 ++- dlls/monstermaker.cpp | 6 +- dlls/soundent.h | 14 +- dlls/soundent_bits.h | 17 ++ dlls/soundscripts.cpp | 103 +++---- dlls/soundscripts.h | 16 +- dlls/util.cpp | 44 --- dlls/util.h | 8 - dlls/visuals.cpp | 58 ++-- dlls/visuals.h | 9 +- dlls/warpball.cpp | 396 ++++++++++++++------------ dlls/warpball.h | 55 +++- game_shared/error_collector.cpp | 10 +- game_shared/file_utils.cpp | 46 +++ game_shared/file_utils.h | 8 + game_shared/fixed_string.h | 134 +++++++++ game_shared/fixed_vector.h | 143 ++++++++++ game_shared/json_config.cpp | 18 ++ game_shared/json_config.h | 17 ++ game_shared/json_utils.cpp | 27 +- game_shared/logger.h | 26 ++ game_shared/random_utils.cpp | 32 +++ game_shared/random_utils.h | 8 + game_shared/template_property_types.h | 33 +++ game_shared/util_shared.cpp | 45 +++ game_shared/util_shared.h | 8 + tests/CMakeLists.txt | 37 +++ tests/fixed_string_test.cpp | 105 +++++++ tests/fixed_vector_test.cpp | 122 ++++++++ tests/main.cpp | 62 ++++ tests/parsetext_test.cpp | 56 ++++ tests/soundscripts_test.cpp | 69 +++++ tests/visuals_test.cpp | 91 ++++++ tests/warpball_test.cpp | 361 +++++++++++++++++++++++ 42 files changed, 1861 insertions(+), 457 deletions(-) create mode 100644 dlls/soundent_bits.h create mode 100644 game_shared/file_utils.cpp create mode 100644 game_shared/file_utils.h create mode 100644 game_shared/fixed_string.h create mode 100644 game_shared/fixed_vector.h create mode 100644 game_shared/json_config.cpp create mode 100644 game_shared/json_config.h create mode 100644 game_shared/logger.h create mode 100644 game_shared/random_utils.cpp create mode 100644 game_shared/random_utils.h create mode 100644 tests/CMakeLists.txt create mode 100644 tests/fixed_string_test.cpp create mode 100644 tests/fixed_vector_test.cpp create mode 100644 tests/main.cpp create mode 100644 tests/parsetext_test.cpp create mode 100644 tests/soundscripts_test.cpp create mode 100644 tests/visuals_test.cpp create mode 100644 tests/warpball_test.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2744e93a90..d52bd6f027 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -99,6 +99,18 @@ jobs: Remove-Item -Force -Path dist/${{ steps.extract_gamedir.outputs.gamedir }}/cl_dlls/client.lib Remove-Item -Force -Path dist/${{ steps.extract_gamedir.outputs.gamedir }}/dlls/hl.lib + - name: Install Google Test + if: startsWith(matrix.os, 'ubuntu') + run: sudo apt -y install libgtest-dev + + - name: Run tests + if: startsWith(matrix.os, 'ubuntu') + working-directory: ./tests + run: | + cmake -GNinja -B build -S . + cmake --build build --target all + ./build/test + - name: Upload linux artifact if: startsWith(matrix.os, 'ubuntu') uses: actions/upload-artifact@v4 diff --git a/cl_dll/CMakeLists.txt b/cl_dll/CMakeLists.txt index 9594ec07ce..487786008b 100644 --- a/cl_dll/CMakeLists.txt +++ b/cl_dll/CMakeLists.txt @@ -132,8 +132,11 @@ set (CLDLL_SOURCES ../game_shared/tex_materials.cpp ../game_shared/parsetext.cpp ../game_shared/error_collector.cpp + ../game_shared/file_utils.cpp ../game_shared/fx_types.cpp + ../game_shared/json_config.cpp ../game_shared/json_utils.cpp + ../game_shared/random_utils.cpp ../game_shared/util_shared.cpp saytext.cpp scoreboard.cpp diff --git a/dlls/CMakeLists.txt b/dlls/CMakeLists.txt index 6e7bbc9f14..02f0a6dd91 100644 --- a/dlls/CMakeLists.txt +++ b/dlls/CMakeLists.txt @@ -25,6 +25,8 @@ project (SVDLL) set (SVDLL_LIBRARY server) +add_definitions(-DSERVER_DLL) + if(NOT MSVC) add_compile_options(-fno-exceptions) # GCC/Clang flag add_compile_options(-fno-rtti) # GCC/Clang flag @@ -201,8 +203,11 @@ set (SVDLL_SOURCES ../game_shared/tex_materials.cpp ../game_shared/parsetext.cpp ../game_shared/error_collector.cpp + ../game_shared/file_utils.cpp ../game_shared/fx_types.cpp + ../game_shared/json_config.cpp ../game_shared/json_utils.cpp + ../game_shared/random_utils.cpp ../game_shared/util_shared.cpp ../game_shared/vcs_info.cpp ) diff --git a/dlls/agrunt.cpp b/dlls/agrunt.cpp index 81a78d93ae..12dd77886e 100644 --- a/dlls/agrunt.cpp +++ b/dlls/agrunt.cpp @@ -332,15 +332,15 @@ void CAGrunt::PrescheduleThink( void ) const SoundScript* myIdleSoundScript = GetSoundScript(idleSoundScript.name); if (myIdleSoundScript) { - if (myIdleSoundScript->waveCount == 1) + if (myIdleSoundScript->waves.size() == 1) { num = 0; } - else if (myIdleSoundScript->waveCount > 1) + else if (myIdleSoundScript->waves.size() > 1) { do { - num = RANDOM_LONG(0, myIdleSoundScript->waveCount-1); + num = RANDOM_LONG(0, myIdleSoundScript->waves.size()-1); } while( num == m_iLastWord ); } diff --git a/dlls/ammoregistry.cpp b/dlls/ammoregistry.cpp index b14d6d30d8..a53938120f 100644 --- a/dlls/ammoregistry.cpp +++ b/dlls/ammoregistry.cpp @@ -1,24 +1,14 @@ #include #include "ammoregistry.h" - -#if CLIENT_DLL -#include "cl_dll.h" -#define AMMO_LOG gEngfuncs.Con_DPrintf -#define AMMO_ERROR gEngfuncs.Con_Printf -#else -#include "util.h" -#define AMMO_LOG(...) ALERT(at_aiconsole, ##__VA_ARGS__ ) -#define AMMO_ERROR(...) ALERT(at_error, ##__VA_ARGS__ ) -#endif #include "arraysize.h" - +#include "logger.h" +#include "string_utils.h" void AmmoType::SetName(const char *ammoName) { #if CLIENT_DLL - strncpy(name, ammoName, sizeof(name)); - name[sizeof(name)-1] = '\0'; + strncpyEnsureTermination(name, ammoName); #else name = ammoName; #endif @@ -42,20 +32,20 @@ int AmmoRegistry::Register(const char *name, int maxAmmo, bool exhaustible) const AmmoType* type = GetByIndex(index); if (type->maxAmmo != maxAmmo || type->exhaustible != exhaustible) { - AMMO_ERROR("Trying to re-register ammo '%s' with different parameters\n", name); + LOG_ERROR("Trying to re-register ammo '%s' with different parameters\n", name); } return index; } if (lastAmmoIndex >= MAX_AMMO_TYPES - 1) { - AMMO_ERROR("Too many ammo types. Max is %d\n", MAX_AMMO_TYPES-1); + LOG_ERROR("Too many ammo types. Max is %d\n", MAX_AMMO_TYPES-1); return -1; } if (maxAmmo <= 0) { - AMMO_ERROR("Invalid max ammo (%d) for '%s'\n", maxAmmo, name); + LOG_ERROR("Invalid max ammo (%d) for '%s'\n", maxAmmo, name); return -1; } @@ -74,7 +64,7 @@ void AmmoRegistry::RegisterOnClient(const char *name, int maxAmmo, int index, bo return; if (index <= 0 || index >= MAX_AMMO_TYPES) { - AMMO_ERROR("Invalid ammo index %d\n", index); + LOG_ERROR("Invalid ammo index %d\n", index); return; } AmmoType& type = ammoTypes[index - 1]; @@ -158,7 +148,7 @@ void AmmoRegistry::ReportRegisteredTypes() void AmmoRegistry::ReportRegisteredType(const AmmoType& ammoType) { - AMMO_LOG("%s. Max ammo: %d. Index: %d. %s\n", ammoType.name, ammoType.maxAmmo, ammoType.id, ammoType.exhaustible ? "Exhaustible" : ""); + LOG_DEV("%s. Max ammo: %d. Index: %d. %s\n", ammoType.name, ammoType.maxAmmo, ammoType.id, ammoType.exhaustible ? "Exhaustible" : ""); } AmmoRegistry g_AmmoRegistry; diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp index d1f094acf6..7df63e40dc 100644 --- a/dlls/cbase.cpp +++ b/dlls/cbase.cpp @@ -857,7 +857,7 @@ void CBaseEntity::EmitSoundScriptAmbient(const Vector& vecOrigin, const char *na void CBaseEntity::PrecacheSoundScript(const SoundScript& soundScript) { - for (size_t i=0; imessage = warpTarget; } - inline string_t WarpballSound1() { + inline const char* WarpballSound1() { if (!FStringNull(pev->noise1)) - return pev->noise1; - return g_modFeatures.alien_teleport_sound ? MAKE_STRING(ALIEN_TELEPORT_SOUND) : MAKE_STRING(WARPBALL_SOUND1); + return STRING(pev->noise1); + return g_modFeatures.alien_teleport_sound ? ALIEN_TELEPORT_SOUND : WARPBALL_SOUND1; } - inline string_t WarpballSound2() { + inline const char* WarpballSound2() { if (FStringNull(pev->noise2)) { if (g_modFeatures.alien_teleport_sound) - return iStringNull; - return FStringNull(pev->noise1) ? MAKE_STRING(WARPBALL_SOUND2) : iStringNull; + return nullptr; + return FStringNull(pev->noise1) ? WARPBALL_SOUND2 : nullptr; } else - return pev->noise2; + return STRING(pev->noise2); } inline float SoundAttenuation() { return ::SoundAttenuation(m_soundRadius); @@ -3107,11 +3107,11 @@ void CEnvWarpBall::Precache( void ) PRECACHE_MODEL( STRING(model2) ); } - PRECACHE_SOUND(STRING(WarpballSound1())); + PRECACHE_SOUND(WarpballSound1()); - string_t sound2 = WarpballSound2(); - if (!FStringNull(sound2)) - PRECACHE_SOUND(STRING(sound2)); + const char* sound2 = WarpballSound2(); + if (sound2 != nullptr) + PRECACHE_SOUND(sound2); UTIL_PrecacheOther("warpball_hurt"); } @@ -3173,13 +3173,13 @@ void CEnvWarpBall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE if (!FBitSet(pev->spawnflags, SF_WARPBALL_NOSOUND)) { - w.sound1.soundName = WarpballSound1(); + w.sound1.sound = WarpballSound1(); w.sound1.volume = SoundVolume(); w.sound1.attenuation = SoundAttenuation(); if (WarpballSound2()) { - w.sound2.soundName = WarpballSound2(); + w.sound2.sound = WarpballSound2(); w.sound2.volume = SoundVolume(); w.sound2.attenuation = SoundAttenuation(); } @@ -3192,7 +3192,7 @@ void CEnvWarpBall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE w.shake.radius = Radius(); } - w.sprite1.spriteName = pev->model ? pev->model : MAKE_STRING(WARPBALL_SPRITE); + w.sprite1.sprite = pev->model ? STRING(pev->model) : WARPBALL_SPRITE; w.sprite1.framerate = SpriteFramerate(); int red = pev->rendercolor.x; @@ -3210,7 +3210,7 @@ void CEnvWarpBall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE w.sprite1.scale = Scale(); if (!FStringNull(model2)) { - w.sprite2.spriteName = model2; + w.sprite2.sprite = STRING(model2); w.sprite2.framerate = framerate2 > 0 ? framerate2 : SpriteFramerate(); int red2, green2, blue2; if (rendercolor2 == g_vecZero) @@ -3556,13 +3556,13 @@ void CEnvXenMaker::TrySpawn() WarpballTemplate w; - w.sprite1.spriteName = MAKE_STRING(XENMAKER_SPRITE1); + w.sprite1.sprite = XENMAKER_SPRITE1; w.sprite1.color = Color(m_vStartSpriteColor.x, m_vStartSpriteColor.y, m_vStartSpriteColor.z); w.sprite1.alpha = m_iStartSpriteAlpha; w.sprite1.scale = m_flStartSpriteScale; w.sprite1.framerate = m_flStartSpriteFramerate; - w.sprite2.spriteName = MAKE_STRING(XENMAKER_SPRITE2); + w.sprite2.sprite = XENMAKER_SPRITE2; w.sprite2.color = Color(m_vEndSpriteColor.x, m_vEndSpriteColor.y, m_vEndSpriteColor.z); w.sprite2.alpha = m_iEndSpriteAlpha; w.sprite2.scale = m_flEndSpriteScale; @@ -3580,15 +3580,15 @@ void CEnvXenMaker::TrySpawn() w.light.radius = m_flLightRadius; if (g_modFeatures.alien_teleport_sound) - w.sound1.soundName = MAKE_STRING(ALIEN_TELEPORT_SOUND); + w.sound1.sound = ALIEN_TELEPORT_SOUND; else - w.sound1.soundName = MAKE_STRING(XENMAKER_SOUND1); + w.sound1.sound = XENMAKER_SOUND1; if (!g_modFeatures.alien_teleport_sound) { if (asTemplate) { - w.sound2.soundName = MAKE_STRING(XENMAKER_SOUND2); + w.sound2.sound = XENMAKER_SOUND2; } else { diff --git a/dlls/game.cpp b/dlls/game.cpp index 64a0c00024..b8ebf38375 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -25,6 +25,7 @@ #include "inventory.h" #include "soundscripts.h" #include "visuals.h" +#include "warpball.h" #include "ent_templates.h" #include "followers.h" #include "savetitles.h" @@ -1361,7 +1362,34 @@ void ReportSoundReplacements() g_soundReplacement.ReportSoundReplacements(); } -extern void DumpWarpballTemplates(); +void ReportSoundScripts() +{ + int argc = CMD_ARGC(); + if (argc > 1) + { + for (int i=1; i 1) + { + for (int i=1; i #include @@ -20,40 +21,23 @@ const char* soundScriptsSchema = R"( } )"; -SoundScript::SoundScript(): waves(), waveCount(0), channel(CHAN_AUTO), volume(VOL_NORM), attenuation(ATTN_NORM), pitch(PITCH_NORM) {} +SoundScript::SoundScript(): waves(), channel(CHAN_AUTO), volume(VOL_NORM), attenuation(ATTN_NORM), pitch(PITCH_NORM) {} SoundScript::SoundScript(int soundChannel, std::initializer_list sounds, FloatRange soundVolume, float soundAttenuation, IntRange soundPitch) - : channel(soundChannel), volume(soundVolume), attenuation(soundAttenuation), pitch(soundPitch) -{ - SetSoundList(sounds); -} + : waves(sounds), channel(soundChannel), volume(soundVolume), attenuation(soundAttenuation), pitch(soundPitch) +{} SoundScript::SoundScript(int soundChannel, std::initializer_list sounds, IntRange soundPitch) - : channel(soundChannel), volume(VOL_NORM), attenuation(ATTN_NORM), pitch(soundPitch) -{ - SetSoundList(sounds); -} - -void SoundScript::SetSoundList(std::initializer_list sounds) -{ - waveCount = Q_min(sounds.size(), MAX_RANDOM_SOUNDS); - size_t i = 0; - for (auto it = sounds.begin(); it != sounds.end(); ++it) - { - waves[i] = *it; - ++i; - if (i >= waveCount) - break; - } -} + : waves(sounds), channel(soundChannel), volume(VOL_NORM), attenuation(ATTN_NORM), pitch(soundPitch) +{} const char* SoundScript::Wave() const { - if (waveCount > 1) + if (waves.size() > 1) { - return waves[RANDOM_LONG(0, waveCount - 1)]; + return waves[RandomInt(0, waves.size() - 1)]; } - else if (waveCount == 1) + else if (waves.size() == 1) { return waves[0]; } @@ -208,12 +192,13 @@ static bool ParseAttenuation(const char* str, float& attenuation) return false; } -bool SoundScriptSystem::ReadFromFile(const char *fileName) +const char* SoundScriptSystem::Schema() const { - Document document; - if (!ReadJsonDocumentWithSchemaFromFile(document, fileName, soundScriptsSchema)) - return false; + return soundScriptsSchema; +} +bool SoundScriptSystem::ReadFromDocument(Document& document, const char* fileName) +{ for (auto scriptIt = document.MemberBegin(); scriptIt != document.MemberEnd(); ++scriptIt) { const char* name = scriptIt->name.GetString(); @@ -238,8 +223,7 @@ void SoundScriptSystem::AddSoundScriptFromJsonValue(const char *name, Value &val if (it != value.MemberEnd()) { Value::Array arr = it->value.GetArray(); - soundScript.waveCount = arr.Size(); - for (size_t i=0; ic_str(); + soundScript.waves.push_back(strIt->c_str()); } soundScriptMeta.wavesSet = true; } @@ -310,7 +294,6 @@ void SoundScriptSystem::EnsureExistingScriptDefined(SoundScript& existing, Sound if (!meta.wavesSet) { existing.waves = soundScript.waves; - existing.waveCount = soundScript.waveCount; } if (!meta.channelSet) @@ -403,66 +386,66 @@ const SoundScript* SoundScriptSystem::ProvideDefaultSoundScript(const char *deri void SoundScriptSystem::DumpSoundScriptImpl(const char *name, const SoundScript &soundScript, const SoundScriptMeta &meta) { - ALERT(at_console, "%s:\n", name); + LOG("%s:\n", name); - ALERT(at_console, "Waves: "); + LOG("Waves: "); if (meta.wavesSet) { - for (size_t i=0; i 1) - { - for (int i=1; i #include "rapidjson/document.h" #include "template_property_types.h" +#include "json_config.h" #include #include #include +#include "fixed_vector.h" #include "icase_compare.h" constexpr size_t MAX_RANDOM_SOUNDS = 10; @@ -22,17 +24,13 @@ struct SoundScript SoundScript(); SoundScript(int soundChannel, std::initializer_list sounds, FloatRange soundVolume, float soundAttenuation, IntRange soundPitch = PITCH_NORM); SoundScript(int soundChannel, std::initializer_list sounds, IntRange soundPitch = PITCH_NORM); - std::array waves; - size_t waveCount; + fixed_vector waves; int channel; FloatRange volume; float attenuation; IntRange pitch; const char* Wave() const; - -private: - void SetSoundList(std::initializer_list sounds); }; struct NamedSoundScript : public SoundScript @@ -95,16 +93,18 @@ struct SoundScriptMeta bool pitchSet = false; }; -class SoundScriptSystem +class SoundScriptSystem : public JSONConfig { public: - bool ReadFromFile(const char* fileName); void AddSoundScriptFromJsonValue(const char* name, rapidjson::Value& value); const SoundScript* GetSoundScript(const char* name); const SoundScript* ProvideDefaultSoundScript(const char* name, const SoundScript& soundScript); const SoundScript* ProvideDefaultSoundScript(const char* derivative, const char* base, const SoundScript& soundScript, const SoundScriptParamOverride paramOverride = SoundScriptParamOverride()); void DumpSoundScripts(); void DumpSoundScript(const char* name); +protected: + const char* Schema() const; + bool ReadFromDocument(rapidjson::Document& document, const char* fileName); private: void DumpSoundScriptImpl(const char* name, const SoundScript& soundScript, const SoundScriptMeta& meta); void EnsureExistingScriptDefined(SoundScript& existing, SoundScriptMeta& meta, const SoundScript& soundScript); @@ -118,6 +118,4 @@ class SoundScriptSystem extern SoundScriptSystem g_SoundScriptSystem; -void DumpSoundScripts(); - #endif diff --git a/dlls/util.cpp b/dlls/util.cpp index 3146f77422..f2c86b6e9d 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -2785,50 +2785,6 @@ void ReportAIStateByClassname(const char* name) } } -const char* RenderModeToString(int rendermode) -{ - switch (rendermode) { - case kRenderNormal: - return "Normal"; - case kRenderTransColor: - return "Color"; - case kRenderTransTexture: - return "Texture"; - case kRenderGlow: - return "Glow"; - case kRenderTransAlpha: - return "Solid"; - case kRenderTransAdd: - return "Additive"; - default: - return "Unknown"; - } -} - -const char* RenderFxToString(int renderfx) -{ - switch (renderfx) { - case kRenderFxNone: return "Normal"; - case kRenderFxPulseSlow: return "Slow Pulse"; - case kRenderFxPulseFast: return "Fast Pulse"; - case kRenderFxPulseSlowWide:return "Slow Wide Pulse"; - case kRenderFxFadeSlow: return "Slow Fade Away"; - case kRenderFxFadeFast: return "Fast Fade Away"; - case kRenderFxSolidSlow: return "Slow Become Solid"; - case kRenderFxSolidFast: return "Fast Become Solid"; - case kRenderFxStrobeSlow: return "Slow Strobe"; - case kRenderFxStrobeFast: return "Fast Strobe"; - case kRenderFxStrobeFaster: return "Faster Strobe"; - case kRenderFxFlickerSlow: return "Slow Flicker"; - case kRenderFxFlickerFast: return "Fast Flicker"; - case kRenderFxNoDissipation:return "Constant Glow"; - case kRenderFxDistort: return "Distort"; - case kRenderFxHologram: return "Hologram"; - case kRenderFxGlowShell: return "Glow Shell"; - default: return "Unknown"; - } -} - // LRC- change the origin to the given position, and bring any movewiths along too. void UTIL_AssignOrigin( CBaseEntity *pEntity, const Vector vecOrigin ) { diff --git a/dlls/util.h b/dlls/util.h index df5b85c648..7814afa4e7 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -89,11 +89,6 @@ inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) #define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) -// Keeps clutter down a bit, when using a float as a bit-vector -#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) -#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) -#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) - // Makes these more explicit, and easier to find #define FILE_GLOBAL static #define DLL_GLOBAL @@ -602,9 +597,6 @@ int RandomizeNumberFromRange(int minI, int maxI); void ReportAIStateByClassname(const char* name); -const char* RenderModeToString(int rendermode); -const char* RenderFxToString(int renderfx); - inline Vector VectorFromColor(const Color& color) { return Vector(color.r, color.g, color.b); } diff --git a/dlls/visuals.cpp b/dlls/visuals.cpp index b176c03ac3..cd2e58464d 100644 --- a/dlls/visuals.cpp +++ b/dlls/visuals.cpp @@ -1,7 +1,8 @@ -#include "util.h" #include "visuals.h" #include "customentity.h" #include "error_collector.h" +#include "logger.h" +#include "util_shared.h" #include "json_utils.h" @@ -20,7 +21,9 @@ void Visual::DoPrecache() { if (HasModel()) { +#if SERVER_DLL modelIndex = PRECACHE_MODEL(model); +#endif } } @@ -123,12 +126,13 @@ static bool ParseRenderFx(const char* str, int& renderfx) return false; } -bool VisualSystem::ReadFromFile(const char *fileName) +const char* VisualSystem::Schema() const { - Document document; - if (!ReadJsonDocumentWithSchemaFromFile(document, fileName, visualsSchema)) - return false; + return visualsSchema; +} +bool VisualSystem::ReadFromDocument(Document& document, const char *fileName) +{ for (auto scriptIt = document.MemberBegin(); scriptIt != document.MemberEnd(); ++scriptIt) { const char* name = scriptIt->name.GetString(); @@ -168,7 +172,7 @@ void VisualSystem::AddVisualFromJsonValue(const char *name, Value &value) { if (visual.HasDefined(Visual::MODEL_DEFINED)) { - ALERT(at_warning, "Visual \"s\" has both 'model' and 'sprite' properties defined!\n", name); + LOG_WARNING("Visual \"%s\" has both 'model' and 'sprite' properties defined!\n", name); } else { @@ -350,21 +354,21 @@ static void PrintRange(const char* name, FloatRange range) { if (range.max <= range.min) { - ALERT(at_console, "%s: %g. ", name, range.min); + LOG("%s: %g. ", name, range.min); } else { - ALERT(at_console, "%s: %g-%g. ", name, range.min, range.max); + LOG("%s: %g-%g. ", name, range.min, range.max); } } void VisualSystem::DumpVisualImpl(const char *name, const Visual &visual) { - ALERT(at_console, "%s:\n", name); + LOG("%s:\n", name); - ALERT(at_console, "Model/Sprite: \"%s\"\n", visual.model ? visual.model : ""); + LOG("Model/Sprite: \"%s\"\n", visual.model ? visual.model : ""); - ALERT(at_console, "Rendermode: %s. Color: (%d, %d, %d). Alpha: %d. Renderfx: %s. ", + LOG("Rendermode: %s. Color: (%d, %d, %d). Alpha: %d. Renderfx: %s. ", RenderModeToString(visual.rendermode), visual.rendercolor.r, visual.rendercolor.g, visual.rendercolor.b, visual.renderamt, @@ -375,7 +379,7 @@ void VisualSystem::DumpVisualImpl(const char *name, const Visual &visual) if (visual.HasDefined(Visual::BEAMWIDTH_DEFINED)) { - ALERT(at_console, "Beam width: %d. Beam noise: %d. Beam scoll rate: %d. ", visual.beamWidth, visual.beamNoise, visual.beamScrollRate); + LOG("Beam width: %d. Beam noise: %d. Beam scoll rate: %d. ", visual.beamWidth, visual.beamNoise, visual.beamScrollRate); } if (visual.HasDefined(Visual::LIFE_DEFINED)) @@ -387,29 +391,29 @@ void VisualSystem::DumpVisualImpl(const char *name, const Visual &visual) { if (visual.radius.max <= visual.radius.min) { - ALERT(at_console, "Radius: %d. ", visual.radius.min); + LOG("Radius: %d. ", visual.radius.min); } else { - ALERT(at_console, "Radius: %d-%d. ", visual.radius.min, visual.radius.max); + LOG("Radius: %d-%d. ", visual.radius.min, visual.radius.max); } } if (visual.HasDefined(Visual::BEAMFLAGS_DEFINED)) { const int beamFlags = visual.beamFlags; - ALERT(at_console, "Beam flags: "); + LOG("Beam flags: "); if (FBitSet(beamFlags, BEAM_FSINE)) - ALERT(at_console, "Sine; "); + LOG("Sine; "); if (FBitSet(beamFlags, BEAM_FSOLID)) - ALERT(at_console, "Solid; "); + LOG("Solid; "); if (FBitSet(beamFlags, BEAM_FSHADEIN)) - ALERT(at_console, "Shadein; "); + LOG("Shadein; "); if (FBitSet(beamFlags, BEAM_FSHADEOUT)) - ALERT(at_console, "Shadeout; "); + LOG("Shadeout; "); } - ALERT(at_console, "\n\n"); + LOG("\n\n"); } void VisualSystem::DumpVisuals() @@ -446,19 +450,7 @@ void VisualSystem::DumpVisual(const char *name) return; } } - ALERT(at_console, "Couldn't find a visual for %s\n", name); + LOG("Couldn't find a visual for %s\n", name); } VisualSystem g_VisualSystem; - -void DumpVisuals() -{ - int argc = CMD_ARGC(); - if (argc > 1) - { - for (int i=1; i #include @@ -257,10 +258,9 @@ struct BuildVisual NamedVisual visual; }; -class VisualSystem +class VisualSystem : public JSONConfig { public: - bool ReadFromFile(const char* fileName); void AddVisualFromJsonValue(const char* name, rapidjson::Value& value); void EnsureVisualExists(const std::string& name); const Visual* GetVisual(const char* name); @@ -268,6 +268,9 @@ class VisualSystem const Visual* ProvideDefaultVisual(const char* name, const Visual& visual, const char* mixinName, const Visual& mixinVisual); void DumpVisuals(); void DumpVisual(const char* name); +protected: + const char* Schema() const; + bool ReadFromDocument(rapidjson::Document& document, const char* fileName); private: void DumpVisualImpl(const char* name, const Visual& visual); @@ -278,6 +281,4 @@ class VisualSystem extern VisualSystem g_VisualSystem; -void DumpVisuals(); - #endif diff --git a/dlls/warpball.cpp b/dlls/warpball.cpp index 3a867f0003..e1115bf301 100644 --- a/dlls/warpball.cpp +++ b/dlls/warpball.cpp @@ -1,20 +1,31 @@ #include #include -#include -#include #include #include #include "error_collector.h" #include "parsetext.h" -#include "extdll.h" -#include "util.h" #include "color_utils.h" #include "json_utils.h" -#include "game.h" +#include "logger.h" + +#include "warpball.h" + +#include "soundent_bits.h" +#if SERVER_DLL #include "effects.h" +#include "game.h" #include "soundent.h" -#include "warpball.h" +#endif + +static bool AlienTeleportSoundEnabled() +{ +#if SERVER_DLL + return g_modFeatures.alien_teleport_sound; +#else + return false; +#endif +} using namespace rapidjson; @@ -242,7 +253,7 @@ static WarpballSprite DefaultWarpballSprite2() static WarpballSound DefaultWarpballSound1() { WarpballSound sound; - if (g_modFeatures.alien_teleport_sound) + if (AlienTeleportSoundEnabled()) sound.sound = ALIEN_TELEPORT_SOUND; else sound.sound = WARPBALL_SOUND1; @@ -252,7 +263,7 @@ static WarpballSound DefaultWarpballSound1() static WarpballSound DefaultWarpballSound2() { WarpballSound sound; - if (!g_modFeatures.alien_teleport_sound) + if (!AlienTeleportSoundEnabled()) sound.sound = WARPBALL_SOUND2; return sound; } @@ -276,97 +287,99 @@ static WarpballTemplate DefaultWarpballTemplate() return w; } -struct WarpballTemplateCatalog +const char* WarpballTemplateCatalog::Schema() const { + return warpballCatalogSchema; +} + +bool WarpballTemplateCatalog::ReadFromDocument(Document& document, const char* fileName) { - std::map > entityMappings; - std::map templates; + bool fullSuccess = true; - WarpballTemplate* GetWarpballTemplate(const char* warpballName, const char* entityClassname) + auto templatesIt = document.FindMember("templates"); + if (templatesIt != document.MemberEnd()) { - auto warpballTemplate = GetWarpballTemplateByName(warpballName); - if (warpballTemplate) - return warpballTemplate; - if (entityClassname && *entityClassname) + auto& templates = templatesIt->value; + for (auto templateIt = templates.MemberBegin(); templateIt != templates.MemberEnd(); ++templateIt) { - auto mappingIt = entityMappings.find(warpballName); - if (mappingIt != entityMappings.end()) - { - auto& mapping = mappingIt->second; - auto entityIt = mapping.find(entityClassname); - if (entityIt != mapping.end()) - { - warpballTemplate = GetWarpballTemplateByName(entityIt->second.c_str()); - if (warpballTemplate) - return warpballTemplate; - } - auto defaultIt = mapping.find("default"); - if (defaultIt != mapping.end()) - { - return GetWarpballTemplateByName(defaultIt->second.c_str()); - } - } + if (!AddWarpballTemplate(templates, templateIt->name.GetString(), templateIt->value, fileName)) + fullSuccess = false; } - return nullptr; } -private: - WarpballTemplate* GetWarpballTemplateByName(const char* warpballName) + auto mappingsIt = document.FindMember("entity_mappings"); + if (mappingsIt != document.MemberEnd()) { - auto it = templates.find(warpballName); - if (it != templates.end()) + auto& entityMappings = mappingsIt->value; + for (auto mappingIt = entityMappings.MemberBegin(); mappingIt != entityMappings.MemberEnd(); mappingIt++) { - return &it->second; + const char* mappingName = mappingIt->name.GetString(); + std::map mapping; + auto& mappingJson = mappingIt->value; + for (auto pairIt = mappingJson.MemberBegin(); pairIt != mappingJson.MemberEnd(); ++pairIt) + { + auto entityName = pairIt->name.GetString(); + auto warpballName = pairIt->value.GetString(); + if (_templates.find(warpballName) == _templates.end()) + { + g_errorCollector.AddFormattedError("%s: entity mapping '%s' refers to nonexistent template '%s'", fileName, mappingName, warpballName); + } + mapping[entityName] = warpballName; + } + if (mapping.find("default") == mapping.end()) + { + g_errorCollector.AddFormattedError("%s: entity mapping '%s' doesn't define 'default' template", fileName, mappingName); + } + else + { + _entityMappings[mappingName] = mapping; + } } - return nullptr; } -}; -static void AssignWarpballSound(WarpballSound& sound, Value& soundJson) + return fullSuccess; +} + +const WarpballTemplate* WarpballTemplateCatalog::FindWarpballTemplate(const char* warpballName, const char* entityClassname) { - if (soundJson.IsNull()) - { - sound = WarpballSound(); - } - else - { - UpdatePropertyFromJson(sound.sound, soundJson, "sound"); - UpdatePropertyFromJson(sound.volume, soundJson, "volume"); - UpdatePropertyFromJson(sound.pitch, soundJson, "pitch"); - UpdatePropertyFromJson(sound.attenuation, soundJson, "attenuation"); - } + return GetWarpballTemplateMutable(warpballName, entityClassname); } -static void AssignWarpballSprite(WarpballSprite& sprite, Value& spriteJson) +WarpballTemplate* WarpballTemplateCatalog::GetWarpballTemplateMutable(const char* warpballName, const char* entityClassname) { - if (spriteJson.IsNull()) - { - sprite = WarpballSprite(); - } - else + auto warpballTemplate = GetWarpballTemplateByName(warpballName); + if (warpballTemplate) + return warpballTemplate; + if (entityClassname && *entityClassname) { - UpdatePropertyFromJson(sprite.sprite, spriteJson, "sprite"); - UpdatePropertyFromJson(sprite.color, spriteJson, "color"); - UpdatePropertyFromJson(sprite.alpha, spriteJson, "alpha"); - UpdatePropertyFromJson(sprite.scale, spriteJson, "scale"); - UpdatePropertyFromJson(sprite.framerate, spriteJson, "framerate"); + auto mappingIt = _entityMappings.find(warpballName); + if (mappingIt != _entityMappings.end()) + { + auto& mapping = mappingIt->second; + auto entityIt = mapping.find(entityClassname); + if (entityIt != mapping.end()) + { + warpballTemplate = GetWarpballTemplateByName(entityIt->second.c_str()); + if (warpballTemplate) + return warpballTemplate; + } + auto defaultIt = mapping.find("default"); + if (defaultIt != mapping.end()) + { + return GetWarpballTemplateByName(defaultIt->second.c_str()); + } + } } + return nullptr; } -static void AssignWarpballBeam(WarpballBeam& beam, Value& beamJson) +WarpballTemplate* WarpballTemplateCatalog::GetWarpballTemplateByName(const char* warpballName) { - if (beamJson.IsNull()) - { - beam = WarpballBeam(); - } - else + auto it = _templates.find(warpballName); + if (it != _templates.end()) { - UpdatePropertyFromJson(beam.sprite, beamJson, "sprite"); - UpdatePropertyFromJson(beam.color, beamJson, "color"); - UpdatePropertyFromJson(beam.alpha, beamJson, "alpha"); - UpdatePropertyFromJson(beam.width, beamJson, "width"); - UpdatePropertyFromJson(beam.noise, beamJson, "noise"); - UpdatePropertyFromJson(beam.life, beamJson, "life"); + return &it->second; } + return nullptr; } static void AssignWarpballLight(WarpballLight& light, Value& lightJson) @@ -412,11 +425,11 @@ static void AssignWarpballAiSound(WarpballAiSound& aiSound, Value& aiSoundJson) if (it != aiSoundJson.MemberEnd()) { const char* valStr = it->value.GetString(); - if (FStrEq(valStr, "combat")) + if (stricmp(valStr, "combat") == 0) { aiSound.type = bits_SOUND_COMBAT; } - else if (FStrEq(valStr, "danger")) + else if (stricmp(valStr, "danger") == 0) { aiSound.type = bits_SOUND_DANGER; } @@ -436,7 +449,7 @@ static void AssignWarpballPosition(WarpballPosition& pos, Value& posJson) } } -static bool AddWarpballTemplate(const char* fileName, WarpballTemplateCatalog& catalog, Value& allTemplatesJsonValue, const char* templateName, Value& templateJsonValue, std::vector inheritanceChain = std::vector()) +bool WarpballTemplateCatalog::AddWarpballTemplate(Value& allTemplatesJsonValue, const char* templateName, Value& templateJsonValue, const char* fileName, std::vector inheritanceChain) { if (std::find(inheritanceChain.begin(), inheritanceChain.end(), templateName) != inheritanceChain.end()) { @@ -452,8 +465,8 @@ static bool AddWarpballTemplate(const char* fileName, WarpballTemplateCatalog& c return false; } - auto existingTemplateIt = catalog.templates.find(templateName); - if (existingTemplateIt != catalog.templates.end()) + auto existingTemplateIt = _templates.find(templateName); + if (existingTemplateIt != _templates.end()) { // Already added, has been used as parent for another template return false; @@ -465,8 +478,8 @@ static bool AddWarpballTemplate(const char* fileName, WarpballTemplateCatalog& c if (inheritsIt != templateJsonValue.MemberEnd()) { const char* parentName = inheritsIt->value.GetString(); - existingTemplateIt = catalog.templates.find(parentName); - if (existingTemplateIt != catalog.templates.end()) + existingTemplateIt = _templates.find(parentName); + if (existingTemplateIt != _templates.end()) { warpballTemplate = existingTemplateIt->second; inherited = true; @@ -477,10 +490,10 @@ static bool AddWarpballTemplate(const char* fileName, WarpballTemplateCatalog& c if (parentIt != allTemplatesJsonValue.MemberEnd()) { inheritanceChain.push_back(templateName); - if (AddWarpballTemplate(fileName, catalog, allTemplatesJsonValue, parentName, parentIt->value, inheritanceChain)) + if (AddWarpballTemplate(allTemplatesJsonValue, parentName, parentIt->value, fileName, inheritanceChain)) { - existingTemplateIt = catalog.templates.find(parentName); - if (existingTemplateIt != catalog.templates.end()) + existingTemplateIt = _templates.find(parentName); + if (existingTemplateIt != _templates.end()) { warpballTemplate = existingTemplateIt->second; inherited = true; @@ -546,97 +559,110 @@ static bool AddWarpballTemplate(const char* fileName, WarpballTemplateCatalog& c AssignWarpballPosition(warpballTemplate.position, positionIt->value); } UpdatePropertyFromJson(warpballTemplate.spawnDelay, templateJsonValue, "spawn_delay"); - catalog.templates[templateName] = warpballTemplate; + _templates[templateName] = warpballTemplate; return true; } -WarpballTemplateCatalog g_WarpballCatalog; +void WarpballTemplateCatalog::AssignWarpballSound(WarpballSound& sound, Value &soundJson) +{ + if (soundJson.IsNull()) + { + sound = WarpballSound(); + } + else + { + UpdateStringFromJson(sound.sound, soundJson, "sound"); + UpdatePropertyFromJson(sound.volume, soundJson, "volume"); + UpdatePropertyFromJson(sound.pitch, soundJson, "pitch"); + UpdatePropertyFromJson(sound.attenuation, soundJson, "attenuation"); + } +} -void LoadWarpballTemplates() +void WarpballTemplateCatalog::AssignWarpballSprite(WarpballSprite& sprite, rapidjson::Value& spriteJson) { - const char* fileName = "templates/warpball.json"; - Document document; - if (!ReadJsonDocumentWithSchemaFromFile(document, fileName, warpballCatalogSchema)) - return; + if (spriteJson.IsNull()) + { + sprite = WarpballSprite(); + } + else + { + UpdateStringFromJson(sprite.sprite, spriteJson, "sprite"); + UpdatePropertyFromJson(sprite.color, spriteJson, "color"); + UpdatePropertyFromJson(sprite.alpha, spriteJson, "alpha"); + UpdatePropertyFromJson(sprite.scale, spriteJson, "scale"); + UpdatePropertyFromJson(sprite.framerate, spriteJson, "framerate"); + } +} - auto templatesIt = document.FindMember("templates"); - if (templatesIt != document.MemberEnd()) +void WarpballTemplateCatalog::AssignWarpballBeam(WarpballBeam& beam, rapidjson::Value& beamJson) +{ + if (beamJson.IsNull()) { - auto& templates = templatesIt->value; - for (auto templateIt = templates.MemberBegin(); templateIt != templates.MemberEnd(); ++templateIt) - { - AddWarpballTemplate(fileName, g_WarpballCatalog, templates, templateIt->name.GetString(), templateIt->value); - } + beam = WarpballBeam(); + } + else + { + UpdateStringFromJson(beam.sprite, beamJson, "sprite"); + UpdatePropertyFromJson(beam.color, beamJson, "color"); + UpdatePropertyFromJson(beam.alpha, beamJson, "alpha"); + UpdatePropertyFromJson(beam.width, beamJson, "width"); + UpdatePropertyFromJson(beam.noise, beamJson, "noise"); + UpdatePropertyFromJson(beam.life, beamJson, "life"); } +} - auto mappingsIt = document.FindMember("entity_mappings"); - if (mappingsIt != document.MemberEnd()) +bool WarpballTemplateCatalog::UpdateStringFromJson(const char*& str, rapidjson::Value& jsonValue, const char* key) +{ + auto it = jsonValue.FindMember(key); + if (it != jsonValue.MemberEnd()) { - auto& entityMappings = mappingsIt->value; - for (auto mappingIt = entityMappings.MemberBegin(); mappingIt != entityMappings.MemberEnd(); mappingIt++) - { - const char* mappingName = mappingIt->name.GetString(); - std::map mapping; - auto& mappingJson = mappingIt->value; - for (auto pairIt = mappingJson.MemberBegin(); pairIt != mappingJson.MemberEnd(); ++pairIt) - { - auto entityName = pairIt->name.GetString(); - auto warpballName = pairIt->value.GetString(); - if (g_WarpballCatalog.templates.find(warpballName) == g_WarpballCatalog.templates.end()) - { - g_errorCollector.AddFormattedError("%s: entity mapping '%s' refers to nonexistent template '%s'", fileName, mappingName, warpballName); - } - mapping[entityName] = warpballName; - } - if (mapping.find("default") == mapping.end()) - { - g_errorCollector.AddFormattedError("%s: entity mapping '%s' doesn't define 'default' template", fileName, mappingName); - } - else - { - g_WarpballCatalog.entityMappings[mappingName] = mapping; - } - } + str = MakeConstantString(it->value.GetString()); + return true; } + return false; } +const char* WarpballTemplateCatalog::MakeConstantString(const char* str) +{ + auto strIt = _stringSet.find(str); + if (strIt == _stringSet.end()) + { + auto p = _stringSet.insert(str); + strIt = p.first; + } + return strIt->c_str(); +} + +WarpballTemplateCatalog g_WarpballCatalog; + +#if SERVER_DLL static void PrecaheWarpballSprite(WarpballSprite& sprite) { - if (sprite.sprite.empty()) + if (sprite.sprite) { - sprite.spriteName = iStringNull; - return; + PRECACHE_MODEL(sprite.sprite); } - sprite.spriteName = MAKE_STRING(sprite.sprite.c_str()); - PRECACHE_MODEL(STRING(sprite.spriteName)); } static void PrecacheWarpballSound(WarpballSound& sound) { - if (sound.sound.empty()) + if (sound.sound) { - sound.soundName = iStringNull; - return; + PRECACHE_SOUND(sound.sound); } - sound.soundName = MAKE_STRING(sound.sound.c_str()); - PRECACHE_SOUND(STRING(sound.soundName)); } static void PrecacheWarpballBeam(WarpballBeam& beam) { - if (beam.sprite.empty()) + if (beam.sprite) { - beam.spriteName = iStringNull; - beam.texture = 0; - return; + beam.texture = PRECACHE_MODEL(beam.sprite); } - beam.spriteName = MAKE_STRING(beam.sprite.c_str()); - beam.texture = PRECACHE_MODEL(STRING(beam.spriteName)); } -void PrecacheWarpballTemplate(const char* name, const char* entityClassname) +void WarpballTemplateCatalog::PrecacheWarpballTemplate(const char* name, const char* entityClassname) { - WarpballTemplate* w = g_WarpballCatalog.GetWarpballTemplate(name, entityClassname); + WarpballTemplate* w = g_WarpballCatalog.GetWarpballTemplateMutable(name, entityClassname); if (w) { PrecaheWarpballSprite(w->sprite1); @@ -647,16 +673,11 @@ void PrecacheWarpballTemplate(const char* name, const char* entityClassname) } } -const WarpballTemplate* FindWarpballTemplate(const char* warpballName, const char* entityClassname) -{ - return g_WarpballCatalog.GetWarpballTemplate(warpballName, entityClassname); -} - static void PlayWarpballSprite(const WarpballSprite& sprite, const Vector& vecOrigin) { - if (!FStringNull(sprite.spriteName)) + if (sprite.sprite != nullptr) { - CSprite *pSpr = CSprite::SpriteCreate( STRING(sprite.spriteName), vecOrigin, TRUE ); + CSprite *pSpr = CSprite::SpriteCreate( sprite.sprite, vecOrigin, TRUE ); pSpr->AnimateAndDie(sprite.framerate); pSpr->SetTransparency(sprite.rendermode, sprite.color.r, sprite.color.g, sprite.color.b, sprite.alpha, sprite.renderfx); pSpr->SetScale(sprite.scale > 0 ? sprite.scale : 1.0f); @@ -665,9 +686,9 @@ static void PlayWarpballSprite(const WarpballSprite& sprite, const Vector& vecOr static void PlayWarpballSound(const WarpballSound& sound, const Vector& vecOrigin, edict_t* playSoundEnt) { - if (!FStringNull(sound.soundName)) + if (sound.sound != nullptr) { - UTIL_EmitAmbientSound(playSoundEnt, vecOrigin, STRING(sound.soundName), sound.volume, sound.attenuation, 0, RandomizeNumberFromRange(sound.pitch)); + UTIL_EmitAmbientSound(playSoundEnt, vecOrigin, sound.sound, sound.volume, sound.attenuation, 0, RandomizeNumberFromRange(sound.pitch)); } } @@ -721,14 +742,15 @@ void PlayWarpballEffect(const WarpballTemplate& warpball, const Vector &vecOrigi CSoundEnt::InsertSound(aiSound.type, vecOrigin, aiSound.radius, aiSound.duration); } } +#endif static void ReportWarpballSprite(const WarpballSprite& sprite) { - if (sprite.sprite.empty()) { - ALERT(at_console, "undefined\n"); + if (sprite.sprite == nullptr) { + LOG("undefined\n"); } else { - ALERT(at_console, "'%s'. Color: (%d, %d, %d). Alpha: %d. Scale: %g. Framerate: %g\n", - sprite.sprite.c_str(), + LOG("'%s'. Color: (%d, %d, %d). Alpha: %d. Scale: %g. Framerate: %g\n", + sprite.sprite, sprite.color.r, sprite.color.g, sprite.color.b, sprite.alpha, sprite.scale, sprite.framerate); } @@ -736,20 +758,20 @@ static void ReportWarpballSprite(const WarpballSprite& sprite) static void ReportWarpballSound(const WarpballSound& sound) { - if (sound.sound.empty()) { - ALERT(at_console, "undefined\n"); + if (sound.sound == nullptr) { + LOG("undefined\n"); } else { - ALERT(at_console, "'%s'. Volume: %g. Attenuation: %g. Pitch: %d\n", sound.sound.c_str(), sound.volume, sound.attenuation, sound.pitch); + LOG("'%s'. Volume: %g. Attenuation: %g. Pitch: %d-%d\n", sound.sound, sound.volume, sound.attenuation, sound.pitch.min, sound.pitch.max); } } static void ReportWarpballBeam(const WarpballBeam& beam) { - if (beam.sprite.empty()) { - ALERT(at_console, "undefined\n"); + if (beam.sprite == nullptr) { + LOG("undefined\n"); } else { - ALERT(at_console, "'%s. Color: (%d, %d, %d). Alpha: %d. Width: %d. Noise: %d. Life: %g-%g\n", - beam.sprite.c_str(), beam.color.r, beam.color.g, beam.color.b, beam.alpha, + LOG("'%s. Color: (%d, %d, %d). Alpha: %d. Width: %d. Noise: %d. Life: %g-%g\n", + beam.sprite, beam.color.r, beam.color.g, beam.color.b, beam.alpha, beam.width, beam.noise, beam.life.min, beam.life.max); } } @@ -757,65 +779,65 @@ static void ReportWarpballBeam(const WarpballBeam& beam) static void ReportWarpballLight(const WarpballLight& light) { if (!light.IsDefined()) { - ALERT(at_console, "undefined\n"); + LOG("undefined\n"); } else { - ALERT(at_console, "Color: (%d, %d, %d). Radius: %d. Life: %g\n", light.color.r, light.color.g, light.color.b, light.radius, light.life); + LOG("Color: (%d, %d, %d). Radius: %d. Life: %g\n", light.color.r, light.color.g, light.color.b, light.radius, light.life); } } static void ReportWarpballShake(const WarpballShake& shake) { if (!shake.IsDefined()) { - ALERT(at_console, "undefined\n"); + LOG("undefined\n"); } else { - ALERT(at_console, "Amplitude: %d. Frequency: %g. Duration: %g. Radius: %d\n", shake.amplitude, shake.frequency, shake.duration, shake.radius); + LOG("Amplitude: %d. Frequency: %g. Duration: %g. Radius: %d\n", shake.amplitude, shake.frequency, shake.duration, shake.radius); } } static void ReportWarpballAiSound(const WarpballAiSound& aiSound) { if (!aiSound.IsDefined()) { - ALERT(at_console, "undefined\n"); + LOG("undefined\n"); } else { - ALERT(at_console, "Type: %d. Radius: %d. Duration: %g\n", aiSound.type, aiSound.radius, aiSound.duration); + LOG("Type: %d. Radius: %d. Duration: %g\n", aiSound.type, aiSound.radius, aiSound.duration); } } -void DumpWarpballTemplates() +void WarpballTemplateCatalog::DumpWarpballTemplates() { - for (auto it = g_WarpballCatalog.templates.begin(); it != g_WarpballCatalog.templates.end(); ++it) + for (auto it = _templates.begin(); it != _templates.end(); ++it) { const WarpballTemplate& w = it->second; - ALERT(at_console, "Warpball '%s'\n", it->first.c_str()); + LOG("Warpball '%s'\n", it->first.c_str()); - ALERT(at_console, "Sprite 1: "); + LOG("Sprite 1: "); ReportWarpballSprite(w.sprite1); - ALERT(at_console, "Sprite 2: "); + LOG("Sprite 2: "); ReportWarpballSprite(w.sprite2); - ALERT(at_console, "Sound 1: "); + LOG("Sound 1: "); ReportWarpballSound(w.sound1); - ALERT(at_console, "Sound 2: "); + LOG("Sound 2: "); ReportWarpballSound(w.sound2); - ALERT(at_console, "Beam: "); + LOG("Beam: "); ReportWarpballBeam(w.beam); - ALERT(at_console, "Beam radius: %d\n", w.beamRadius); - ALERT(at_console, "Beam count: %d-%d\n", w.beamCount.min, w.beamCount.max); + LOG("Beam radius: %d\n", w.beamRadius); + LOG("Beam count: %d-%d\n", w.beamCount.min, w.beamCount.max); - ALERT(at_console, "Light: "); + LOG("Light: "); ReportWarpballLight(w.light); - ALERT(at_console, "Shake: "); + LOG("Shake: "); ReportWarpballShake(w.shake); - ALERT(at_console, "Delay before monster spawn: %g\n", w.spawnDelay); + LOG("Delay before monster spawn: %g\n", w.spawnDelay); if (!w.position.IsDefined()) - ALERT(at_console, "Position: default\n"); + LOG("Position: default\n"); else - ALERT(at_console, "Position: Vertical shift: %g\n", w.position.verticalShift); + LOG("Position: Vertical shift: %g\n", w.position.verticalShift); - ALERT(at_console, "\n"); + LOG("\n"); } } diff --git a/dlls/warpball.h b/dlls/warpball.h index 9a9f439062..d3c8b8b600 100644 --- a/dlls/warpball.h +++ b/dlls/warpball.h @@ -2,8 +2,15 @@ #ifndef WARPBALL_H #define WARPBALL_H +#include +#include #include +#include +#include "const_render.h" +#include "const_sound.h" #include "template_property_types.h" +#include "rapidjson/document.h" +#include "json_config.h" #define WARPBALL_RED_DEFAULT 77 #define WARPBALL_GREEN_DEFAULT 210 @@ -23,9 +30,8 @@ struct WarpballSound { - WarpballSound(): sound(), soundName(0), volume(1.0f), attenuation(0.8f), pitch(100) {} - std::string sound; - string_t soundName; + WarpballSound(): sound(), volume(1.0f), attenuation(ATTN_NORM), pitch(100) {} + const char* sound; float volume; float attenuation; IntRange pitch; @@ -35,7 +41,6 @@ struct WarpballSprite { WarpballSprite(): sprite(), - spriteName(0), color(), alpha(255), scale(1.0f), @@ -43,8 +48,7 @@ struct WarpballSprite rendermode(kRenderGlow), renderfx(kRenderFxNoDissipation) {} - std::string sprite; - string_t spriteName; + const char* sprite; Color color; int alpha; float scale; @@ -57,15 +61,13 @@ struct WarpballBeam { WarpballBeam(): sprite(), - spriteName(0), texture(0), color(), alpha(220), width(30), noise(65), life(0.5f, 1.6f) {} - std::string sprite; - string_t spriteName; + const char* sprite; int texture; Color color; int alpha; @@ -149,9 +151,38 @@ struct WarpballTemplate WarpballPosition position; }; -void LoadWarpballTemplates(); -void PrecacheWarpballTemplate(const char* name, const char* entityClassname); -const WarpballTemplate* FindWarpballTemplate(const char* warpballName, const char* entityClassname); +struct WarpballTemplateCatalog : public JSONConfig +{ +protected: + const char* Schema() const override; + bool ReadFromDocument(rapidjson::Document& document, const char* fileName) override; + +public: + const WarpballTemplate* FindWarpballTemplate(const char* warpballName, const char* entityClassname = nullptr); + void PrecacheWarpballTemplate(const char* name, const char* entityClassname); + void DumpWarpballTemplates(); + +private: + WarpballTemplate* GetWarpballTemplateMutable(const char* warpballName, const char* entityClassname); + WarpballTemplate* GetWarpballTemplateByName(const char* warpballName); + bool AddWarpballTemplate(rapidjson::Value& allTemplatesJsonValue, const char* templateName, rapidjson::Value& templateJsonValue, const char* fileName, std::vector inheritanceChain = std::vector()); + + void AssignWarpballSound(WarpballSound& sound, rapidjson::Value& soundJson); + void AssignWarpballSprite(WarpballSprite& sprite, rapidjson::Value& spriteJson); + void AssignWarpballBeam(WarpballBeam& beam, rapidjson::Value& beamJson); + + bool UpdateStringFromJson(const char*& str, rapidjson::Value& jsonValue, const char* key); + const char* MakeConstantString(const char* str); + + std::map > _entityMappings; + std::map _templates; + std::set _stringSet; +}; + +extern WarpballTemplateCatalog g_WarpballCatalog; + +#if SERVER_DLL void PlayWarpballEffect(const WarpballTemplate& warpballTemplate, const Vector& vecOrigin, edict_t* playSoundEnt); +#endif #endif diff --git a/game_shared/error_collector.cpp b/game_shared/error_collector.cpp index 70e1d3a2f0..35440948b6 100644 --- a/game_shared/error_collector.cpp +++ b/game_shared/error_collector.cpp @@ -1,20 +1,14 @@ #include "error_collector.h" +#include "logger.h" #include #include -#ifndef CLIENT_DLL -#include "extdll.h" -#include "enginecallback.h" -#endif - void ErrorCollector::AddError(const char *str) { if (str) { _errors.push_back(str); -#ifndef CLIENT_DLL - ALERT(at_error, "%s\n", str); -#endif + LOG_ERROR("%s\n", str); } } diff --git a/game_shared/file_utils.cpp b/game_shared/file_utils.cpp new file mode 100644 index 0000000000..c66bb866d4 --- /dev/null +++ b/game_shared/file_utils.cpp @@ -0,0 +1,46 @@ +#include "file_utils.h" + +#if CLIENT_DLL +#include "cl_dll.h" +#elif SERVER_DLL +#include "extdll.h" +#include "enginecallback.h" +#else +#include +#include +#endif + +char* ReadFileContents(const char* fileName, int& fileSize) +{ + char *pMemFile = nullptr; + +#if CLIENT_DLL + pMemFile = (char*)gEngfuncs.COM_LoadFile( fileName, 5, &fileSize ); +#elif SERVER_DLL + pMemFile = (char*)g_engfuncs.pfnLoadFileForMe( fileName, &fileSize ); +#else + FILE* f = fopen(fileName, "r"); + if (f) { + fseek(f, 0, SEEK_END); + fileSize = (int)ftell(f); + fseek(f, 0, SEEK_SET); + pMemFile = (char*)malloc(fileSize); + if (pMemFile) { + fread(pMemFile, 1, fileSize, f); + } + fclose(f); + } +#endif + return pMemFile; +} + +void FreeFileContents(char* pMemFile) +{ +#if CLIENT_DLL + gEngfuncs.COM_FreeFile(pMemFile); +#elif SERVER_DLL + g_engfuncs.pfnFreeFile(pMemFile); +#else + free(pMemFile); +#endif +} diff --git a/game_shared/file_utils.h b/game_shared/file_utils.h new file mode 100644 index 0000000000..8a6a0c5692 --- /dev/null +++ b/game_shared/file_utils.h @@ -0,0 +1,8 @@ +#pragma once +#ifndef FILE_UTILS_H +#define FILE_UTILS_H + +char* ReadFileContents(const char* fileName, int& fileSize); +void FreeFileContents(char* pMemFile); + +#endif diff --git a/game_shared/fixed_string.h b/game_shared/fixed_string.h new file mode 100644 index 0000000000..bac23ab7c4 --- /dev/null +++ b/game_shared/fixed_string.h @@ -0,0 +1,134 @@ +#pragma once +#ifndef FIXED_STRING_H +#define FIXED_STRING_H + +#include +#include +#include "string_utils.h" + +template +struct fixed_string +{ + typedef char value_type; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef char& reference; + typedef const char& const_reference; + typedef char* pointer; + typedef const char* const_pointer; + typedef const char* iterator; + typedef const char* const_iterator; + + fixed_string() { + init(); + } + fixed_string(const char* str) { + setString(str); + } + template + fixed_string(const fixed_string& o) { + static_assert(K <= N, "Can't convert string of bigger size to the string of smaller size"); + setString(o.c_str()); + } + fixed_string& operator=(const char* str) { + setString(str); + return *this; + } + template + fixed_string& operator=(const fixed_string& o) { + static_assert(K <= N, "Can't convert string of bigger size to the string of smaller size"); + setString(o.c_str()); + return *this; + } + bool operator==(const char* str) const { + return strncmp(str, _a, _size) == 0; + } + bool operator!=(const char* str) const { + return strncmp(str, _a, _size) != 0; + } + bool operator<=(const char* str) const { + return strncmp(str, _a, _size) <= 0; + } + bool operator<(const char* str) const { + return strncmp(str, _a, _size) < 0; + } + bool operator>=(const char* str) const { + return strncmp(str, _a, _size) >= 0; + } + bool operator>(const char* str) const { + return strncmp(str, _a, _size) > 0; + } + template + bool operator==(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) == 0; + } + template + bool operator!=(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) != 0; + } + template + bool operator<=(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) <= 0; + } + template + bool operator<(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) < 0; + } + template + bool operator>=(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) >= 0; + } + template + bool operator>(const fixed_string& s) const { + return strcmp(c_str(), s.c_str()) > 0; + } + char operator[](size_t i) const { + return _a[i]; + } + size_t size() const { + return _size; + } + bool empty() const { + return _size == 0; + } + void clear() { + init(); + } + iterator begin() const { + return cbegin(); + } + iterator end() const { + return cend(); + } + const_iterator cbegin() const { + return _a; + } + const_iterator cend() const { + return _a + _size; + } + const char* c_str() const { + return _a; + } +private: + void init() + { + _size = 0; + _a[0] = '\0'; + } + void setString(const char* str) + { + if (str == nullptr) + { + init(); + } + else + { + strncpyEnsureTermination(_a, str); + _size = strlen(_a); + } + } + char _a[N]; + size_t _size; +}; + +#endif diff --git a/game_shared/fixed_vector.h b/game_shared/fixed_vector.h new file mode 100644 index 0000000000..bfcf8c8896 --- /dev/null +++ b/game_shared/fixed_vector.h @@ -0,0 +1,143 @@ +#ifndef FIXED_ARRAY_H +#define FIXED_ARRAY_H + +#include +#include +#include +#include +#include "min_and_max.h" + +template +struct fixed_vector +{ +private: + typedef std::array array_type; +public: + typedef typename array_type::value_type value_type; + typedef typename array_type::size_type size_type; + typedef typename array_type::difference_type difference_type; + typedef typename array_type::reference reference; + typedef typename array_type::const_reference const_reference; + typedef typename array_type::pointer pointer; + typedef typename array_type::const_pointer const_pointer; + typedef typename array_type::iterator iterator; + typedef typename array_type::const_iterator const_iterator; + + fixed_vector(): _count(0) {} + template + fixed_vector(std::initializer_list list) + { + setList(std::move(list)); + } + template + fixed_vector(const fixed_vector& o) + { + setVector(o); + } + template + fixed_vector& operator=(std::initializer_list list) + { + setList(std::move(list)); + return *this; + } + template + fixed_vector& operator=(const fixed_vector& o) + { + setVector(o); + } + bool operator==(const fixed_vector& o) const + { + return size() == o.size() && std::equal(cbegin(), cend(), o.cbegin()); + } + bool operator!=(const fixed_vector& o) const + { + return size() != o.size() || !std::equal(cbegin(), cend(), o.cbegin()); + } + T& operator[](size_t i) { + return _a[i]; + } + const T& operator[](size_t i) const { + return _a[i]; + } + size_t size() const { + return _count; + } + size_t count() const { + return _count; + } + constexpr size_t capacity() const { + return N; + } + bool empty() const { + return _count == 0; + } + void push_back(const T& t) { + if(_count < N) { + _a[_count++] = t; + } + } + void pop_back() { + if (_count > 0) { + _count--; + } + } + void clear() { + _count = 0; + } + iterator begin() { + return _a.begin(); + } + iterator end() { + return _a.begin() + _count; + } + const_iterator begin() const { + return cbegin(); + } + const_iterator end() const { + return cend(); + } + const_iterator cbegin() const { + return _a.cbegin(); + } + const_iterator cend() const { + return _a.cbegin() + _count; + } + reference front() { + return _a.front(); + } + const_reference front() const { + return _a.front(); + } + reference back() { + return _count > 0 ? _a[_count-1] : _a[0]; + } + const_reference back() const { + return _count > 0 ? _a[_count-1] : _a[0]; + } +private: + template + void setList(std::initializer_list&& list) { + _count = Q_min(list.size(), N); + size_t i = 0; + for (auto it = list.begin(); it != list.end(); ++it) + { + _a[i] = *it; + ++i; + if (i >= _count) + break; + } + } + template + void setVector(const fixed_vector& o) { + static_assert(M <= N, "Can't copy vector of bigger size into the vector of smaller size"); + + clear(); + for (const auto& val : o) { + push_back(val); + } + } + array_type _a; + size_t _count = 0; +}; + +#endif diff --git a/game_shared/json_config.cpp b/game_shared/json_config.cpp new file mode 100644 index 0000000000..d888aa003c --- /dev/null +++ b/game_shared/json_config.cpp @@ -0,0 +1,18 @@ +#include "json_config.h" +#include "json_utils.h" + +bool JSONConfig::ReadFromFile(const char *fileName) +{ + rapidjson::Document document; + if (!ReadJsonDocumentWithSchemaFromFile(document, fileName, Schema())) + return false; + return ReadFromDocument(document, fileName); +} + +bool JSONConfig::ReadFromContents(const char *contents, const char *fileName) +{ + rapidjson::Document document; + if (!ReadJsonDocumentWithSchema(document, contents, strlen(contents), Schema(), fileName)) + return false; + return ReadFromDocument(document, fileName); +} diff --git a/game_shared/json_config.h b/game_shared/json_config.h new file mode 100644 index 0000000000..87ddde5707 --- /dev/null +++ b/game_shared/json_config.h @@ -0,0 +1,17 @@ +#pragma once +#ifndef JSON_CONFIG_H +#define JSON_CONFIG_H + +#include "rapidjson/document.h" + +class JSONConfig +{ +public: + bool ReadFromFile(const char* fileName); + bool ReadFromContents(const char* contents, const char* fileName); +protected: + virtual const char* Schema() const = 0; + virtual bool ReadFromDocument(rapidjson::Document& document, const char* fileName) = 0; +}; + +#endif diff --git a/game_shared/json_utils.cpp b/game_shared/json_utils.cpp index 0761dca768..c81ce36557 100644 --- a/game_shared/json_utils.cpp +++ b/game_shared/json_utils.cpp @@ -1,11 +1,6 @@ -#if CLIENT_DLL -#include "cl_dll.h" -#else -#include "extdll.h" -#include "enginecallback.h" -#endif - +#include "file_utils.h" #include "json_utils.h" +#include "logger.h" #include "color_utils.h" #include "parsetext.h" @@ -358,26 +353,14 @@ bool ReadJsonDocumentWithSchemaFromFile(Document &document, const char *fileName { int fileSize; char *pMemFile = nullptr; -#if CLIENT_DLL - pMemFile = (char*)gEngfuncs.COM_LoadFile( fileName, 5, &fileSize ); -#else - pMemFile = (char*)g_engfuncs.pfnLoadFileForMe( fileName, &fileSize ); -#endif + pMemFile = ReadFileContents(fileName, fileSize); if (!pMemFile) return false; -#if CLIENT_DLL - gEngfuncs.Con_DPrintf("Parsing %s\n", fileName); -#else - ALERT(at_console, "Parsing %s\n", fileName); -#endif + LOG("Parsing %s\n", fileName); const bool success = ReadJsonDocumentWithSchema(document, pMemFile, fileSize, schemaText, fileName); -#if CLIENT_DLL - gEngfuncs.COM_FreeFile(pMemFile); -#else - g_engfuncs.pfnFreeFile(pMemFile); -#endif + FreeFileContents(pMemFile); return success; } diff --git a/game_shared/logger.h b/game_shared/logger.h new file mode 100644 index 0000000000..e8b0ec992e --- /dev/null +++ b/game_shared/logger.h @@ -0,0 +1,26 @@ +#pragma once +#ifndef LOGGER_H +#define LOGGER_H + +#if CLIENT_DLL +#include "cl_dll.h" +#define LOG gEngfuncs.Con_DPrintf +#define LOG_DEV gEngfuncs.Con_DPrintf +#define LOG_WARNING gEngfuncs.Con_DPrintf +#define LOG_ERROR gEngfuncs.Con_Printf +#elif SERVER_DLL +#include "extdll.h" +#include "enginecallback.h" +#define LOG(...) ALERT(at_console, ##__VA_ARGS__) +#define LOG_DEV(...) ALERT(at_aiconsole, ##__VA_ARGS__) +#define LOG_WARNING(...) ALERT(at_warning, ##__VA_ARGS__) +#define LOG_ERROR(...) ALERT(at_error, ##__VA_ARGS__) +#else +#include +#define LOG printf +#define LOG_DEV printf +#define LOG_WARNING(...) fprintf(stderr, ##__VA_ARGS__) +#define LOG_ERROR(...) fprintf(stderr, ##__VA_ARGS__) +#endif + +#endif diff --git a/game_shared/random_utils.cpp b/game_shared/random_utils.cpp new file mode 100644 index 0000000000..953bd03984 --- /dev/null +++ b/game_shared/random_utils.cpp @@ -0,0 +1,32 @@ +#include "random_utils.h" + +#if CLIENT_DLL +#include "cl_dll.h" +#elif SERVER_DLL +#include "extdll.h" +#include "enginecallback.h" +#else +#include +#endif + +int RandomInt(int low, int high) +{ +#if CLIENT_DLL + return gEngfuncs.pfnRandomLong(low, high); +#elif SERVER_DLL + return RANDOM_LONG(low, high); +#else + return low + rand() % (high-low+1); +#endif +} + +float RandomFloat(float low, float high) +{ +#if CLIENT_DLL + return gEngfuncs.pfnRandomFloat(low, high); +#elif SERVER_DLL + return RANDOM_FLOAT(low, high); +#else + return low + (rand()/(float)RAND_MAX) * (high - low); +#endif +} diff --git a/game_shared/random_utils.h b/game_shared/random_utils.h new file mode 100644 index 0000000000..59f7edaaa2 --- /dev/null +++ b/game_shared/random_utils.h @@ -0,0 +1,8 @@ +#pragma once +#ifndef RANDOM_UTILS_H +#define RANDOM_UTILS_H + +int RandomInt(int low, int high); +float RandomFloat(float low, float high); + +#endif diff --git a/game_shared/template_property_types.h b/game_shared/template_property_types.h index afd981bb35..55bdbd9f70 100644 --- a/game_shared/template_property_types.h +++ b/game_shared/template_property_types.h @@ -9,6 +9,19 @@ struct Color int r; int g; int b; + + constexpr inline bool operator==(Color o) const + { + return IsEqual(o); + } + constexpr inline bool operator!=(Color o) const + { + return !IsEqual(o); + } +private: + constexpr inline bool IsEqual(const Color& o) const { + return r == o.r && g == o.g && b == o.b; + } }; template @@ -19,6 +32,26 @@ struct NumberRange constexpr NumberRange(N val): min(val), max(val) {} N min; N max; + + constexpr inline bool operator==(const NumberRange& o) const { + return IsEqual(o); + } + constexpr inline bool operator==(const N& o) const { + return IsEqual(o); + } + constexpr inline bool operator!=(const NumberRange& o) const { + return !IsEqual(o); + } + constexpr inline bool operator!=(const N& o) const { + return !IsEqual(o); + } +private: + constexpr inline bool IsEqual(const NumberRange& o) const { + return min == o.min && max == o.max; + } + constexpr inline bool IsEqual(const N& o) const { + return min == o && max == o; + } }; typedef NumberRange FloatRange; diff --git a/game_shared/util_shared.cpp b/game_shared/util_shared.cpp index 4c078f850b..4374b3083a 100644 --- a/game_shared/util_shared.cpp +++ b/game_shared/util_shared.cpp @@ -1,5 +1,6 @@ #include "util_shared.h" #include "string_utils.h" +#include "const_render.h" #include #include @@ -221,3 +222,47 @@ void UTIL_StringToVector( float *pVector, const char *pString, int* componentsRe pVector[j] = 0; } } + +const char* RenderModeToString(int rendermode) +{ + switch (rendermode) { + case kRenderNormal: + return "Normal"; + case kRenderTransColor: + return "Color"; + case kRenderTransTexture: + return "Texture"; + case kRenderGlow: + return "Glow"; + case kRenderTransAlpha: + return "Solid"; + case kRenderTransAdd: + return "Additive"; + default: + return "Unknown"; + } +} + +const char* RenderFxToString(int renderfx) +{ + switch (renderfx) { + case kRenderFxNone: return "Normal"; + case kRenderFxPulseSlow: return "Slow Pulse"; + case kRenderFxPulseFast: return "Fast Pulse"; + case kRenderFxPulseSlowWide:return "Slow Wide Pulse"; + case kRenderFxFadeSlow: return "Slow Fade Away"; + case kRenderFxFadeFast: return "Fast Fade Away"; + case kRenderFxSolidSlow: return "Slow Become Solid"; + case kRenderFxSolidFast: return "Fast Become Solid"; + case kRenderFxStrobeSlow: return "Slow Strobe"; + case kRenderFxStrobeFast: return "Fast Strobe"; + case kRenderFxStrobeFaster: return "Faster Strobe"; + case kRenderFxFlickerSlow: return "Slow Flicker"; + case kRenderFxFlickerFast: return "Fast Flicker"; + case kRenderFxNoDissipation:return "Constant Glow"; + case kRenderFxDistort: return "Distort"; + case kRenderFxHologram: return "Hologram"; + case kRenderFxGlowShell: return "Glow Shell"; + default: return "Unknown"; + } +} diff --git a/game_shared/util_shared.h b/game_shared/util_shared.h index 1eabe7868c..358b6d76b1 100644 --- a/game_shared/util_shared.h +++ b/game_shared/util_shared.h @@ -14,4 +14,12 @@ extern float UTIL_AngleDistance( float next, float cur ); extern void UTIL_StringToVector( float *pVector, const char *pString, int* componentsRead = nullptr ); +const char* RenderModeToString(int rendermode); +const char* RenderFxToString(int renderfx); + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) | (bits)) +#define ClearBits(flBitVector, bits) ((flBitVector) = (int)(flBitVector) & ~(bits)) +#define FBitSet(flBitVector, bit) ((int)(flBitVector) & (bit)) + #endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..7e728ce174 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,37 @@ +set (CMAKE_CXX_STANDARD 11) + +cmake_minimum_required(VERSION 3.9) + +if(NOT ${CMAKE_VERSION} VERSION_LESS "3.15.0") + cmake_policy(SET CMP0091 NEW) +endif() + +project (HLSDK-FEATUREFUL-TESTS) + +if(NOT MSVC) + add_definitions(-Dstricmp=strcasecmp -Dstrnicmp=strncasecmp -D_snprintf=snprintf -D_vsnprintf=vsnprintf ) +endif() + +include_directories (. ../common ../engine ../pm_shared ../game_shared ../dlls) + +add_executable(test + fixed_string_test.cpp + fixed_vector_test.cpp + parsetext_test.cpp + soundscripts_test.cpp + visuals_test.cpp + warpball_test.cpp + ../game_shared/error_collector.cpp + ../game_shared/file_utils.cpp + ../game_shared/json_config.cpp + ../game_shared/json_utils.cpp + ../game_shared/parsetext.cpp + ../game_shared/random_utils.cpp + ../game_shared/util_shared.cpp + ../dlls/soundscripts.cpp + ../dlls/visuals.cpp + ../dlls/warpball.cpp + main.cpp +) + +target_link_libraries(test gtest) diff --git a/tests/fixed_string_test.cpp b/tests/fixed_string_test.cpp new file mode 100644 index 0000000000..2e2cbee9e5 --- /dev/null +++ b/tests/fixed_string_test.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +#include "fixed_string.h" + +TEST(FixedString, DefaultConstructor) { + fixed_string<10> s; + EXPECT_TRUE(s.empty()); + EXPECT_TRUE(s.size() == 0); + EXPECT_TRUE(*s.c_str() == '\0'); +} + +TEST(FixedString, FromCStringConstructor) { + const char* cstr = "Hello"; + fixed_string<10> s = cstr; + + EXPECT_TRUE(s == cstr); + EXPECT_FALSE(s.empty()); + EXPECT_TRUE(s.size() == 5); +} + +TEST(FixedString, CopyConstructor) { + fixed_string<6> s = "Hello"; + fixed_string<6> s2 = s; + EXPECT_TRUE(s2 == "Hello"); +} + +TEST(FixedString, CopyFromFixedStringConstructor) { + fixed_string<4> s = "Boo"; + fixed_string<6> s2 = s; + EXPECT_TRUE(s2 == "Boo"); +} + +TEST(FixedString, AssignmentOperator) { + fixed_string<10> s = "Hello"; + s = "World!"; + EXPECT_TRUE(s == "World!"); + EXPECT_TRUE(s.size() == 6); + + s = "FizzBuzz"; + EXPECT_TRUE(s == "FizzBuzz"); + EXPECT_TRUE(s.size() == 8); + + fixed_string<8> s2 = "TestTest"; + s = s2; + EXPECT_TRUE(s == "TestTest"); +} + +TEST(FixedString, EqualOperator) { + fixed_string<10> s = "Hello"; + fixed_string<6> s2 = "Hello"; + fixed_string<6> s3 = "World"; + EXPECT_TRUE(s == s); + EXPECT_TRUE(s == s2); + EXPECT_TRUE(s != s3); + EXPECT_TRUE(s2 != s3); + EXPECT_FALSE(s == s3); + EXPECT_FALSE(s2 == s3); +} + +TEST(FixedString, ComparisonOperators) { + fixed_string<3> aa = "aa"; + fixed_string<3> ab = "ab"; + fixed_string<3> ba = "ba"; + fixed_string<5> hell = "hell"; + fixed_string<6> hello = "hello"; + + EXPECT_TRUE(aa <= aa); + EXPECT_TRUE(aa <= ab); + EXPECT_TRUE(aa <= ba); + EXPECT_TRUE(aa < ab); + EXPECT_TRUE(aa < ba); + + EXPECT_TRUE(ab >= aa); + EXPECT_TRUE(ab > aa); + + EXPECT_TRUE(ba >= aa); + EXPECT_TRUE(ba > aa); + + EXPECT_TRUE(hell <= hello); + EXPECT_TRUE(hell < hello); + EXPECT_TRUE(hello >= hell); + EXPECT_TRUE(hello > hell); + + std::vector> strings; + strings.push_back("World"); + strings.push_back("Hello"); + strings.push_back("Test"); + std::sort(strings.begin(), strings.end()); + EXPECT_EQ(strings[0], "Hello"); + EXPECT_EQ(strings[1], "Test"); + EXPECT_EQ(strings[2], "World"); +} + +TEST(FixedString, Clear) { + fixed_string<10> s = "Hello"; + s.clear(); + EXPECT_TRUE(s.empty()); +} + +TEST(FixedString, GetCString) { + fixed_string<16> s = "Hello World"; + EXPECT_STREQ(s.c_str(), "Hello World"); +} diff --git a/tests/fixed_vector_test.cpp b/tests/fixed_vector_test.cpp new file mode 100644 index 0000000000..775bf21b3f --- /dev/null +++ b/tests/fixed_vector_test.cpp @@ -0,0 +1,122 @@ +#include +#include + +#include "fixed_string.h" +#include "fixed_vector.h" + +TEST(FixedVector, DefaultConstructor) { + fixed_vector v; + EXPECT_TRUE(v.empty()); + EXPECT_TRUE(v.size() == 0); + EXPECT_TRUE(v.capacity() == 10); +} + +TEST(FixedVector, InitializerListConstructor) { + fixed_vector v = {42, 13, 7}; + EXPECT_FALSE(v.empty()); + EXPECT_TRUE(v.size() == 3); + EXPECT_EQ(v[0], 42); + EXPECT_EQ(v[1], 13); + EXPECT_EQ(v[2], 7); +} + +TEST(FixedVector, CopyConstructor) { + fixed_vector v = {3,2,1}; + fixed_vector v2 = v; + EXPECT_EQ(v2[0], v[0]); + EXPECT_EQ(v2[2], v[2]); + EXPECT_TRUE(v == v2); +} + +TEST(FixedVector, CopyFromFixedVectorConstructor) { + fixed_vector v = {1,2,3,4,5}; + fixed_vector v2 = v; + EXPECT_EQ(v2[0], 1); + EXPECT_EQ(v2[4], 5); + EXPECT_EQ(v2.size(), v.size()); + + fixed_vector v3; + v3 = v; + EXPECT_EQ(v3.size(), v.size()); + EXPECT_EQ(v3[0], v[0]); + EXPECT_EQ(v3[4], v[4]); +} + +TEST(FixedVector, AssignmentOperator) { + fixed_vector v = {0,1}; + v = {4,5,6}; + EXPECT_EQ(v[0], 4); + EXPECT_EQ(v[1], 5); + EXPECT_EQ(v[2], 6); + + fixed_vector v2 = {7,8,9}; + v = v2; + EXPECT_EQ(v[0], 7); + EXPECT_EQ(v[1], 8); + EXPECT_EQ(v[2], 9); + + fixed_vector v3 = {10,11}; + v = v3; + EXPECT_EQ(v.size(), v3.size()); + EXPECT_EQ(v[0], v3[0]); + EXPECT_EQ(v[1], v3[1]); +} + +TEST(FixedVector, EqualOperator) { + fixed_vector v; + fixed_vector v1 = {1,2,3}; + fixed_vector v2 = {1,2,3}; + fixed_vector v3 = {1,2}; + fixed_vector v4 = {1,2,4}; + fixed_vector v5 = {1,2,4}; + EXPECT_TRUE(v != v1); + EXPECT_TRUE(v1 == v2); + EXPECT_TRUE(v1 != v3); + EXPECT_TRUE(v1 != v4); + EXPECT_TRUE(v4 == v5); +} + +TEST(FixedVector, PushAndPopBack) { + fixed_vector v; + v.push_back(4); + EXPECT_EQ(v.size(), 1); + v.push_back(5); + EXPECT_EQ(v.size(), 2); + v.push_back(6); + EXPECT_EQ(v.size(), 3); + + EXPECT_EQ(v[0], 4); + EXPECT_EQ(v[1], 5); + EXPECT_EQ(v[2], 6); + + v.pop_back(); + EXPECT_EQ(v.size(), 2); +} + +TEST(FixedVector, FrontAndBack) { + fixed_vector v = {4,5,6}; + EXPECT_EQ(v.front(), 4); + EXPECT_EQ(v.back(), 6); +} + +TEST(FixedVector, SortAlgorithm) { + fixed_vector v = {5,4,3,2,1}; + std::sort(v.begin(), v.end()); + EXPECT_EQ(v[0], 1); + EXPECT_EQ(v[4], 5); +} + +TEST(FixedVector, FixedStringsVector) { + fixed_vector, 3> v = {"Hello", "World", "Test"}; + EXPECT_EQ(v[0], "Hello"); + EXPECT_EQ(v[1], "World"); + EXPECT_EQ(v[2], "Test"); + + fixed_vector, 3> v2; + v2.push_back("A"); + v2.push_back("B"); + v2.push_back("C"); + EXPECT_EQ(v2[0], "A"); + EXPECT_EQ(v2[1], "B"); + EXPECT_EQ(v2[2], "C"); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000000..9129d0a7f1 --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,62 @@ +#include + +#include "min_and_max.h" +#include "random_utils.h" +#include "string_utils.h" +#include "template_property_types.h" + +TEST(MinAndMax, Cases) { + EXPECT_EQ(Q_min(4, 6), 4); + EXPECT_EQ(Q_min(3.0f, 6), 3); + EXPECT_EQ(Q_min(2, 2), 2); + EXPECT_EQ(Q_max(4, 6), 6); + EXPECT_EQ(Q_max(3.0f, 6), 6); + EXPECT_EQ(Q_max(2, 2), 2); +} + +TEST(StrncpyEnsureTermination, StringLongerThanBuffer) { + char buf[12]; + strncpyEnsureTermination(buf, "Hello, World!"); + EXPECT_STREQ(buf, "Hello, Worl"); +} + +TEST(NumberRange, Equal) { + IntRange r{13, 42}; + IntRange s = 7; + + EXPECT_EQ(r, IntRange(13, 42)); + EXPECT_EQ(r.min, 13); + EXPECT_EQ(r.max, 42); + + EXPECT_EQ(s, 7); + EXPECT_EQ(s.min, 7); + EXPECT_EQ(s.max, 7); +} + +TEST(Random, Int) { + for (int i=0; i<10; ++i) { + const int low = i; + const int high = i + 20; + + const int val = RandomInt(low, high); + EXPECT_TRUE(val >= low); + EXPECT_TRUE(val <= high); + } +} + +TEST(Random, Float) { + for (int i=0; i<10; ++i) { + const float low = 0.0f; + const float high = 1.0f; + + const float val = RandomFloat(low, high); + EXPECT_TRUE(val >= low); + EXPECT_TRUE(val <= high); + } +} + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/parsetext_test.cpp b/tests/parsetext_test.cpp new file mode 100644 index 0000000000..a58cbe84b6 --- /dev/null +++ b/tests/parsetext_test.cpp @@ -0,0 +1,56 @@ +#include +#include "parsetext.h" +#include "color_utils.h" + +TEST(ParseText, Color) { + int packedColor; + int packedColor0x; + int packedColorHtml; + + EXPECT_TRUE(ParseColor("255 8 32", packedColor)); + EXPECT_TRUE(ParseColor("0xFF0820", packedColor0x)); + EXPECT_TRUE(ParseColor("#FF0820", packedColorHtml)); + + EXPECT_EQ(packedColor, 0xFF0820); + EXPECT_EQ(packedColor0x, 0xFF0820); + EXPECT_EQ(packedColorHtml, 0xFF0820); + + int temp; + EXPECT_FALSE(ParseColor("string", temp)); + EXPECT_FALSE(ParseColor("0xstring", temp)); + EXPECT_FALSE(ParseColor("#string", temp)); +} + +TEST(ParseText, Boolean) { + bool zero, falseBool, FalseBool, noBool, NoBool; + bool one, trueBool, TrueBool, yesBool, YesBool; + + EXPECT_TRUE(ParseBoolean("0", zero)); + EXPECT_TRUE(ParseBoolean("false", falseBool)); + EXPECT_TRUE(ParseBoolean("False", FalseBool)); + EXPECT_TRUE(ParseBoolean("no", noBool)); + EXPECT_TRUE(ParseBoolean("No", NoBool)); + + EXPECT_FALSE(zero); + EXPECT_FALSE(falseBool); + EXPECT_FALSE(FalseBool); + EXPECT_FALSE(noBool); + EXPECT_FALSE(NoBool); + + EXPECT_TRUE(ParseBoolean("1", one)); + EXPECT_TRUE(ParseBoolean("true", trueBool)); + EXPECT_TRUE(ParseBoolean("True", TrueBool)); + EXPECT_TRUE(ParseBoolean("yes", yesBool)); + EXPECT_TRUE(ParseBoolean("Yes", YesBool)); + + EXPECT_TRUE(one); + EXPECT_TRUE(trueBool); + EXPECT_TRUE(TrueBool); + EXPECT_TRUE(yesBool); + EXPECT_TRUE(YesBool); + + bool temp; + EXPECT_FALSE(ParseBoolean("arbitrary string", temp)); + EXPECT_FALSE(ParseBoolean("10", temp)); + EXPECT_FALSE(ParseBoolean("2", temp)); +} diff --git a/tests/soundscripts_test.cpp b/tests/soundscripts_test.cpp new file mode 100644 index 0000000000..a18111b7d5 --- /dev/null +++ b/tests/soundscripts_test.cpp @@ -0,0 +1,69 @@ +#include +#include "soundscripts.h" + +const char soundScripts[] = R"( +{ + "Civilian.Pain": { + "waves": ["colette/colette_pain0.wav", "colette/colette_pain1.wav", "colette/colette_pain2.wav", "colette/colette_pain3.wav"], + "channel": "voice", + "pitch": 105, + "volume": 0.9, + "attenuation": "static" + }, + "Civilian.Die": { + "waves": ["colette/colette_die0.wav", "colette/colette_die1.wav", "colette/colette_die2.wav"], + "channel": "body", + "pitch": [95, 105], + "volume": [0.9, 1.0], + "attenuation": "norm" + }, + "Bullsquid.Growl": { + "waves": ["bullsquid_hl2/attackgrowl1.wav", "bullsquid_hl2/attackgrowl2.wav"], + "channel": "weapon", + "attenuation": 1.1 + } +} +)"; + +TEST(SoundScripts, Parse) { + SoundScriptSystem s; + ASSERT_TRUE(s.ReadFromContents(soundScripts, "")); + + { + const SoundScript* civilianPain = s.GetSoundScript("Civilian.Pain"); + ASSERT_TRUE(civilianPain != nullptr); + EXPECT_EQ(civilianPain->waves.size(), 4); + EXPECT_STREQ(civilianPain->waves[0], "colette/colette_pain0.wav"); + EXPECT_STREQ(civilianPain->waves[3], "colette/colette_pain3.wav"); + EXPECT_EQ(civilianPain->channel, CHAN_VOICE); + EXPECT_EQ(civilianPain->pitch.min, 105); + EXPECT_EQ(civilianPain->pitch.min, civilianPain->pitch.max); + EXPECT_EQ(civilianPain->volume.min, 0.9f); + EXPECT_EQ(civilianPain->volume.min, civilianPain->volume.max); + EXPECT_EQ(civilianPain->attenuation, ATTN_STATIC); + } + + { + const SoundScript* civilianDie = s.GetSoundScript("Civilian.Die"); + ASSERT_TRUE(civilianDie != nullptr); + EXPECT_EQ(civilianDie->waves.size(), 3); + EXPECT_STREQ(civilianDie->waves[0], "colette/colette_die0.wav"); + EXPECT_STREQ(civilianDie->waves[2], "colette/colette_die2.wav"); + EXPECT_EQ(civilianDie->channel, CHAN_BODY); + EXPECT_EQ(civilianDie->pitch.min, 95); + EXPECT_EQ(civilianDie->pitch.max, 105); + EXPECT_EQ(civilianDie->volume.min, 0.9f); + EXPECT_EQ(civilianDie->volume.max, 1.0f); + EXPECT_EQ(civilianDie->attenuation, ATTN_NORM); + } + + { + const SoundScript* bullsquidGrowl = s.GetSoundScript("Bullsquid.Growl"); + ASSERT_TRUE(bullsquidGrowl != nullptr); + EXPECT_EQ(bullsquidGrowl->waves.size(), 2); + EXPECT_STREQ(bullsquidGrowl->waves[0], "bullsquid_hl2/attackgrowl1.wav"); + EXPECT_STREQ(bullsquidGrowl->waves[1], "bullsquid_hl2/attackgrowl2.wav"); + EXPECT_EQ(bullsquidGrowl->channel, CHAN_WEAPON); + EXPECT_EQ(bullsquidGrowl->attenuation, 1.1f); + } +} diff --git a/tests/visuals_test.cpp b/tests/visuals_test.cpp new file mode 100644 index 0000000000..147426672c --- /dev/null +++ b/tests/visuals_test.cpp @@ -0,0 +1,91 @@ +#include +#include "visuals.h" +#include "customentity.h" + +const char visuals[] = R"( +{ + "Vortigaunt.ZapBeam": { + "color": [242, 0, 213], + "alpha": 200, + "sprite": "sprites/xsmoke3.spr", + "noise": 40, + "width": 60, + "beamflags": ["sine", "solid"] + }, + "Bullsquid.Spit": { + "sprite": "sprites/e-tele1.spr", + "scale": 0.25, + "rendermode": "Glow", + "renderfx": "Constant Glow", + "framerate": 15.0 + }, + "Hornet.Model": { + "renderfx": "hologram", + }, + "Controller.EnergyBallLight": { + "radius": [32, 48] + }, +} +)"; + +TEST(Visuals, Parse) { + VisualSystem s; + ASSERT_TRUE(s.ReadFromContents(visuals, "")); + + { + const Visual* zapBeam = s.GetVisual("Vortigaunt.ZapBeam"); + ASSERT_TRUE(zapBeam != nullptr); + EXPECT_TRUE(zapBeam->HasDefined(Visual::COLOR_DEFINED)); + EXPECT_EQ(zapBeam->rendercolor, Color(242, 0, 213)); + EXPECT_TRUE(zapBeam->HasDefined(Visual::ALPHA_DEFINED)); + EXPECT_EQ(zapBeam->renderamt, 200); + EXPECT_TRUE(zapBeam->HasDefined(Visual::MODEL_DEFINED)); + EXPECT_STREQ(zapBeam->model, "sprites/xsmoke3.spr"); + EXPECT_TRUE(zapBeam->HasDefined(Visual::BEAMNOISE_DEFINED)); + EXPECT_EQ(zapBeam->beamNoise, 40); + EXPECT_TRUE(zapBeam->HasDefined(Visual::BEAMWIDTH_DEFINED)); + EXPECT_EQ(zapBeam->beamWidth, 60); + EXPECT_TRUE(zapBeam->HasDefined(Visual::BEAMFLAGS_DEFINED)); + EXPECT_EQ(zapBeam->beamFlags, BEAM_FSINE|BEAM_FSOLID); + + EXPECT_FALSE(zapBeam->HasDefined(Visual::RENDERMODE_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::RENDERFX_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::SCALE_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::FRAMERATE_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::BEAMSCROLLRATE_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::LIFE_DEFINED)); + EXPECT_FALSE(zapBeam->HasDefined(Visual::RADIUS_DEFINED)); + } + + { + const Visual* bullsquidSpit = s.GetVisual("Bullsquid.Spit"); + ASSERT_TRUE(bullsquidSpit != nullptr); + EXPECT_STREQ(bullsquidSpit->model, "sprites/e-tele1.spr"); + EXPECT_TRUE(bullsquidSpit->HasDefined(Visual::SCALE_DEFINED)); + EXPECT_EQ(bullsquidSpit->scale, 0.25f); + EXPECT_TRUE(bullsquidSpit->HasDefined(Visual::RENDERMODE_DEFINED)); + EXPECT_EQ(bullsquidSpit->rendermode, kRenderGlow); + EXPECT_TRUE(bullsquidSpit->HasDefined(Visual::RENDERFX_DEFINED)); + EXPECT_EQ(bullsquidSpit->renderfx, kRenderFxNoDissipation); + EXPECT_TRUE(bullsquidSpit->HasDefined(Visual::FRAMERATE_DEFINED)); + EXPECT_EQ(bullsquidSpit->framerate, 15.0f); + } + + { + const Visual* hornetModel = s.GetVisual("Hornet.Model"); + ASSERT_TRUE(hornetModel != nullptr); + EXPECT_EQ(hornetModel->renderfx,kRenderFxHologram); + } + + { + const Visual* energyBallLight = s.GetVisual("Controller.EnergyBallLight"); + ASSERT_TRUE(energyBallLight != nullptr); + EXPECT_TRUE(energyBallLight->HasDefined(Visual::RADIUS_DEFINED)); + EXPECT_EQ(energyBallLight->radius, IntRange(32, 48)); + } + + { + const Visual* nonexistent = s.GetVisual("Nonexistent"); + ASSERT_TRUE(nonexistent == nullptr); + } +} diff --git a/tests/warpball_test.cpp b/tests/warpball_test.cpp new file mode 100644 index 0000000000..5c9522569e --- /dev/null +++ b/tests/warpball_test.cpp @@ -0,0 +1,361 @@ +#include +#include "warpball.h" + +const char warpballs[] = R"( +{ + "entity_mappings": { + "auto": { + // the mapping between monster name and template name + "monster_alien_slave": "xen", + "monster_alien_grunt": "xen_big", + "monster_headcrab": "xen_small", + "monster_pitdrone": "racex", + "monster_shocktrooper": "racex_big", + "default": "xen" + }, + "alt_mapping": { + "default": "xen_alt" + } + }, + "templates": { + "xen": { + "sprite1": { + "sprite": "sprites/fexplo_1.spr", + "alpha": 225, + "color": "77 210 130", + "framerate": 12, + }, + "sprite2": { + "sprite": "sprites/xflare_1.spr", + "alpha": 200, + "color": "70 220 120", + "framerate": 10, + "scale": 1.1 + }, + "sound1": { + "sound": "debris/beamstart_2.wav", + }, + "sound2": { + "sound": "debris/beamstart_7.wav", + }, + "beam": { + "sprite": "sprites/ligtning.spr", + "color": "20 240 20", + "alpha": 240, + "width": 30, + "noise": 65, + "life": [0.8, 1.6] + }, + "beam_radius": 200, + "beam_count": [10, 20], + "light": { + "color": "80 210 130", + "radius": 200 + }, + "spawn_delay": 0.4 + }, + "xen_alt": { + "inherits": "xen", + "beam": { + "color": [217, 226, 146], + "alpha": 128, + "width": 25, + "noise": 50 + }, + "light": { + "color": "39 209 137" + }, + "sprite1": { + "color": "65 209 61" + }, + "sprite2": { + "color": "159 240 214" + }, + "sound1": { + "sound": "debris/alien_teleport.wav" + }, + "sound2": null, + "beam_count": 8 + }, + "xen_small": { + "inherits": "xen", + "sprite1": { + "scale": 0.8 + }, + "sprite2": { + "scale": 0.9 + }, + "sound1": { + "attenuation": 1.0, + "volume": 0.9, + "pitch": 105 + }, + "sound2": { + "attenuation": 1.1, + "volume": 0.8, + "pitch": 110 + }, + "light": { + "radius": 160 + }, + "beam_count": [10, 15], + "spawn_delay": 0.3 + }, + "xen_big": { + "inherits": "xen", + "sprite1": { + "scale": 1.25 + }, + "sprite2": { + "scale": 1.25 + }, + "shake": { + "radius": 200, + "duration": 1.0, + "frequency": 160, + "amplitude": 6 + }, + "sound1": { + "attenuation": 0.6 + }, + "sound2": { + "attenuation": 0.6 + }, + "position": { + "vertical_shift": 42 + } + }, + "racex": { + "inherits": "xen", + "sprite1": { + "sprite": "sprites/xflare2.spr", + "color": "200 100 200" + }, + "sprite2": null, + "beam": { + "color": "240 80 160" + }, + "light": { + "color": "200 100 200" + }, + "sound1": { + "pitch": 105 + }, + "sound2": { + "pitch": 105 + } + }, + "racex_big": { + "inherits": "racex", + "sprite1": { + "scale": 1.25 + }, + "beam_radius": 256, + "shake": { + "radius": 192, + "duration": 1.0, + "frequency": 160, + "amplitude": 6 + }, + "sound1": { + "attenuation": 0.6 + }, + "sound2": { + "attenuation": 0.6 + }, + "position": { + "vertical_shift": 40 + } + } + } +} +)"; + +TEST(WarpballTemplates, Parse) { + WarpballTemplateCatalog c; + ASSERT_TRUE(c.ReadFromContents(warpballs, "")); + + const WarpballTemplate* xen = c.FindWarpballTemplate("xen"); + ASSERT_TRUE(xen != nullptr); + + EXPECT_STREQ(xen->sprite1.sprite, "sprites/fexplo_1.spr"); + EXPECT_EQ(xen->sprite1.alpha, 225); + EXPECT_EQ(xen->sprite1.color, Color(77, 210, 130)); + EXPECT_EQ(xen->sprite1.framerate, 12.0f); + EXPECT_EQ(xen->sprite1.scale, 1.0f); + EXPECT_EQ(xen->sprite1.rendermode, kRenderGlow); + EXPECT_EQ(xen->sprite1.renderfx, kRenderFxNoDissipation); + + EXPECT_STREQ(xen->sprite2.sprite, "sprites/xflare_1.spr"); + EXPECT_EQ(xen->sprite2.alpha, 200); + EXPECT_EQ(xen->sprite2.color, Color(70, 220, 120)); + EXPECT_EQ(xen->sprite2.framerate, 10.f); + EXPECT_EQ(xen->sprite2.scale, 1.1f); + + EXPECT_STREQ(xen->sound1.sound, "debris/beamstart_2.wav"); + EXPECT_EQ(xen->sound1.pitch, 100); + EXPECT_EQ(xen->sound1.volume, 1.0f); + EXPECT_EQ(xen->sound1.attenuation, 0.8f); + + EXPECT_STREQ(xen->sound2.sound, "debris/beamstart_7.wav"); + + EXPECT_STREQ(xen->beam.sprite, "sprites/ligtning.spr"); + EXPECT_EQ(xen->beam.color, Color(20, 240, 20)); + EXPECT_EQ(xen->beam.alpha, 240); + EXPECT_EQ(xen->beam.width, 30); + EXPECT_EQ(xen->beam.noise, 65); + EXPECT_EQ(xen->beam.life, FloatRange(0.8f, 1.6f)); + + EXPECT_EQ(xen->beamRadius, 200); + EXPECT_EQ(xen->beamCount, IntRange(10, 20)); + EXPECT_EQ(xen->spawnDelay, 0.4f); + + EXPECT_EQ(xen->light.color, Color(80, 210, 130)); + EXPECT_EQ(xen->light.radius, 200); + + EXPECT_FALSE(xen->shake.IsDefined()); + EXPECT_FALSE(xen->aiSound.IsDefined()); + EXPECT_FALSE(xen->position.IsDefined()); + + { + const WarpballTemplate* t = c.FindWarpballTemplate("xen_alt"); + ASSERT_TRUE(t != nullptr); + + EXPECT_EQ(t->beam.color, Color(217, 226, 146)); + EXPECT_EQ(t->beam.alpha, 128); + EXPECT_EQ(t->beam.width, 25); + EXPECT_EQ(t->beam.noise, 50); + EXPECT_EQ(t->beam.life, xen->beam.life); + + EXPECT_EQ(t->light.radius, xen->light.radius); + EXPECT_EQ(t->light.color, Color(39, 209, 137)); + + EXPECT_TRUE(strcmp(t->sprite1.sprite, xen->sprite1.sprite) == 0); + EXPECT_EQ(t->sprite1.color, Color(65, 209, 61)); + EXPECT_EQ(t->sprite1.alpha, xen->sprite1.alpha); + EXPECT_EQ(t->sprite1.framerate, xen->sprite1.framerate); + EXPECT_EQ(t->sprite1.scale, xen->sprite1.scale); + + EXPECT_TRUE(strcmp(t->sprite2.sprite, xen->sprite2.sprite) == 0); + EXPECT_EQ(t->sprite2.color, Color(159, 240, 214)); + + EXPECT_STREQ(t->sound1.sound, "debris/alien_teleport.wav"); + + EXPECT_EQ(t->sound2.sound, nullptr); + + EXPECT_EQ(t->beamRadius, xen->beamRadius); + EXPECT_EQ(t->beamCount, 8); + EXPECT_EQ(t->spawnDelay, xen->spawnDelay); + } + + { + const WarpballTemplate* t = c.FindWarpballTemplate("xen_small"); + ASSERT_TRUE(t != nullptr); + + EXPECT_STREQ(t->sprite1.sprite, xen->sprite1.sprite); + EXPECT_EQ(t->sprite1.color, xen->sprite1.color); + EXPECT_EQ(t->sprite1.alpha, xen->sprite1.alpha); + EXPECT_EQ(t->sprite1.framerate, xen->sprite1.framerate); + EXPECT_EQ(t->sprite1.scale, 0.8f); + + EXPECT_STREQ(t->sprite2.sprite, xen->sprite2.sprite); + EXPECT_EQ(t->sprite2.color, xen->sprite2.color); + EXPECT_EQ(t->sprite2.scale, 0.9f); + + EXPECT_STREQ(t->sound1.sound, xen->sound1.sound); + EXPECT_EQ(t->sound1.attenuation, 1.0f); + EXPECT_EQ(t->sound1.volume, 0.9f); + EXPECT_EQ(t->sound1.pitch, 105); + + EXPECT_STREQ(t->sound2.sound, xen->sound2.sound); + EXPECT_EQ(t->sound2.attenuation, 1.1f); + EXPECT_EQ(t->sound2.volume, 0.8f); + EXPECT_EQ(t->sound2.pitch, 110); + + EXPECT_EQ(t->light.color, xen->light.color); + EXPECT_EQ(t->light.radius, 160); + + EXPECT_EQ(t->beamCount, IntRange(10, 15)); + EXPECT_EQ(t->spawnDelay, 0.3f); + } + + { + const WarpballTemplate* t = c.FindWarpballTemplate("xen_big"); + ASSERT_TRUE(t != nullptr); + + EXPECT_EQ(t->sprite1.scale, 1.25f); + + EXPECT_TRUE(t->shake.IsDefined()); + EXPECT_EQ(t->shake.radius, 200); + EXPECT_EQ(t->shake.duration, 1.0f); + EXPECT_EQ(t->shake.frequency, 160); + EXPECT_EQ(t->shake.amplitude, 6); + + EXPECT_EQ(t->sound1.attenuation, 0.6f); + + EXPECT_TRUE(t->position.IsDefined()); + EXPECT_EQ(t->position.verticalShift, 42); + } + + { + const WarpballTemplate* t = c.FindWarpballTemplate("racex_big"); + ASSERT_TRUE(t != nullptr); + EXPECT_STREQ(t->sprite1.sprite, "sprites/xflare2.spr"); + EXPECT_EQ(t->sprite1.color, Color(200, 100, 200)); + + EXPECT_EQ(t->sprite2.sprite, nullptr); + EXPECT_EQ(t->beam.color, Color(240, 80, 160)); + EXPECT_EQ(t->sound1.pitch, 105); + EXPECT_EQ(t->sound1.attenuation, 0.6f); + + EXPECT_TRUE(t->shake.IsDefined()); + } + + { + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_unknown"), xen); + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_alien_slave"), xen); + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_alien_grunt"), c.FindWarpballTemplate("xen_big")); + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_headcrab"), c.FindWarpballTemplate("xen_small")); + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_pitdrone"), c.FindWarpballTemplate("racex")); + EXPECT_EQ(c.FindWarpballTemplate("auto", "monster_shocktrooper"), c.FindWarpballTemplate("racex_big")); + + EXPECT_EQ(c.FindWarpballTemplate("alt_mapping", "monster_unknown"), c.FindWarpballTemplate("xen_alt")); + } +} + +const char looped[] = R"( +{ + "templates": { + "xen": { + "inherits": "xen_alt" + }, + "xen_alt": { + "inherits": "xen_small", + }, + "xen_small": { + "inherits": "xen", + } + } +} +)"; + +TEST(WarpballTemplates, DetectLoop) { + WarpballTemplateCatalog c; + ASSERT_FALSE(c.ReadFromContents(looped, "")); +} + +const char missingParent[] = R"( +{ + "templates": { + "xen": {}, + "xen_alt": { + "inherits": "nonexistent", + } + } +} +)"; + +TEST(WarpballTemplates, MissingParent) { + WarpballTemplateCatalog c; + ASSERT_FALSE(c.ReadFromContents(missingParent, "")); +}