diff --git a/BUILD.md b/BUILD.md deleted file mode 100644 index c2c2c4f..0000000 --- a/BUILD.md +++ /dev/null @@ -1,31 +0,0 @@ -Building StatusSpec plugin -========================== - -Notice ------- -StatusSpec is not yet compatible with Linux & OS X. Due to the advanced features of StatusSpec, no support for platforms other than Windows is planned in the near future. - -Requirements ------------- -* **[Windows]** Visual Studio or Visual Express C++ -* Source SDK 2013 - https://github.com/ValveSoftware/source-sdk-2013 -* Metamod:Source - https://github.com/alliedmodders/metamod-source -* MinHook - https://github.com/TsudaKageyu/minhook -* JsonCpp - https://github.com/open-source-parsers/jsoncpp - -Building --------- -1. Clone this repository in `/mp/src/utils` in source-sdk-2013. -2. Clone Metamod:Source in your directory of choice. -3. Clone MinHook in your directory of choice. -4. Clone JsonCpp in your directory of choice. -5. Build the MinHook project in both Debug and Release configurations with one of the included Visual Studio project directories within its `build` directory. -6. Run `amalgamate.py` within JsonCpp to generate include and source files. -7. Adjust the `MMSOURCE_DEV` macro in `statusspec.vpc` to the location of Metamod:Source. -8. Adjust the `MINHOOK` macro in `statusspec.vpc` to the location of MinHook. -9. Adjust the `MINHOOK_BUILD` macro in `statusspec.vpc` to the location of the MinHook Visual Studio project directory you selected. -10. Adjust the `JSONCPP` macro in `statusspec.vpc` to the location of the generated JsonCpp files (usually within the `dist` directory of the source directory). -11. Copy `build_tools/createstatusspec.bat` or `build_tools/createstatusspec` (depending on platform) to `mp/src` in source-sdk-2013. -12. Add the contents of `build_tools/project.vgc` to `mp/src/vpc_scripts/projects.vgc` in source-sdk-2013. -13. Run the script file you copied in step #2 to generate project files. -14. Build the project with the project files that were generated. \ No newline at end of file diff --git a/DEVELOP.md b/DEVELOP.md new file mode 100644 index 0000000..7737da9 --- /dev/null +++ b/DEVELOP.md @@ -0,0 +1,31 @@ +Developing StatusSpec +===================== + +Notice +------ +StatusSpec is not yet compatible with Linux & OS X. Due to the advanced features of StatusSpec, no support for platforms other than Windows is planned in the near future. + +Requirements +------------ +### Installed Software ### +* **[Windows]** Visual Studio 2013 +* Python 2.6+ + +### Code Resources ### +* Source SDK 2013 - https://github.com/ValveSoftware/source-sdk-2013 +* Metamod:Source - https://github.com/alliedmodders/metamod-source +* MinHook - https://github.com/TsudaKageyu/minhook +* JsonCpp - https://github.com/open-source-parsers/jsoncpp + +Setup +----- +1. Move, copy, or link this repository as necessary such that the files of this repository are located under `mp/src/utils/StatusSpec` within the Source SDK 2013 repository. +2. Build the MinHook project in both Debug and Release configurations with one of the included Visual Studio project directories within its `build` directory. +3. Run `amalgamate.py` within JsonCpp to generate include and source files. +4. Adjust the `MMSOURCE_DEV` macro in `statusspec.vpc` to the path of the Metamod:Source repository on disk. +5. Adjust the `MINHOOK` macro in `statusspec.vpc` to the path of MinHook repository on disk. +6. Adjust the `MINHOOK_BUILD` macro in `statusspec.vpc` to the path of the Visual Studio project directory you used to build MinHook in step #2. +7. Adjust the `JSONCPP` macro in `statusspec.vpc` to the path of the directory containing the generated JsonCpp files from step #3 (usually the `dist` directory in the JsonCpp repository). +8. Add the contents of this repository's `build_tools/project.vgc` to `mp/src/vpc_scripts/projects.vgc` within the Source SDK 2013 repository. +9. Copy this repository's `build_tools/createstatusspec.bat` or `build_tools/createstatusspec` (depending on platform) to `mp/src` within the Source SDK 2013 repository. +10. Run the script file you copied in the previous step to generate platform-appropriate project files. \ No newline at end of file diff --git a/README.md b/README.md index 4b32b36..7af74fd 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,12 @@ a Team Fortress 2 client plugin that augments game spectating Changelog --------- +**0.22.0** +* general + * fixed issues with outlines (hopefully) +* custom models + * new module + **0.21.0** * general * optimized plugin @@ -236,7 +242,7 @@ The configuration file for the freeze info HUD is `Resource/UI/FreezeInfo.res`. * `statusspec_custommaterials_unload_replacement_group ` - unload a material replacement group #### Resource Files -Player model configuration is loaded from the `Resource/CustomMaterials.res` file. Replacement groups should be configured as sections (whose names are used in the commands for this module). Material replacements should be specified by entries with the path of the old material from the `materials` folder as the key and the path of the new material from the `materials` folder as the value. An example of a configured file is given below: +Custom material configuration is loaded from the `Resource/CustomMaterials.res` file. Replacement groups should be configured as sections (whose names are used in the commands for this module). Material replacements should be specified by entries with the path of the old material from the `materials` folder as the key and the path of the new material from the `materials` folder as the value. An example of a configured file is given below: ``` "materials" { @@ -248,6 +254,37 @@ Player model configuration is loaded from the `Resource/CustomMaterials.res` fil } ``` +### Custom Models +*allows models to be swapped out* + +#### Console Variables +* `statusspec_custommodels_enabled` - enable custom models + +#### Console Commands +* `statusspec_custommodels_load_replacement_group ` - load a model replacement group +* `statusspec_custommodels_unload_replacement_group ` - unload a model replacement group + +#### Resource Files +Custom model configuration is loaded from the `Resource/CustomModels.res` file. Replacement groups should be configured as sections (whose names are used in the commands for this module). Material replacements should be specified by entries with the path of the old model as the key and the path of the new model as the value. An example of a configured file is given below: + +``` +"models" +{ + "demo" + { + "models/player/scout.mdl" "models/player/demo.mdl" + "models/player/soldier.mdl" "models/player/demo.mdl" + "models/player/pyro.mdl" "models/player/demo.mdl" + "models/player/demo.mdl" "models/player/demo.mdl" + "models/player/heavy.mdl" "models/player/demo.mdl" + "models/player/engineer.mdl" "models/player/demo.mdl" + "models/player/medic.mdl" "models/player/demo.mdl" + "models/player/sniper.mdl" "models/player/demo.mdl" + "models/player/spy.mdl" "models/player/demo.mdl" + } +} +``` + ### Custom Textures *allows textures to be swapped out* @@ -259,7 +296,7 @@ Player model configuration is loaded from the `Resource/CustomMaterials.res` fil * `statusspec_customtextures_unload_replacement_group ` - unload a texture replacement group #### Resource Files -Player model configuration is loaded from the `Resource/CustomTextures.res` file. Replacement groups should be configured as sections (whose names are used in the commands for this module). Material replacements should be specified by entries with the path of the old texture from the `materials` folder as the key and the path of the new texture from the `materials` folder as the value. An example of a configured file is given below: +Custom texture configuration is loaded from the `Resource/CustomTextures.res` file. Replacement groups should be configured as sections (whose names are used in the commands for this module). Texture replacements should be specified by entries with the path of the old texture from the `materials` folder as the key and the path of the new texture from the `materials` folder as the value. An example of a configured file is given below: ``` "textures" { @@ -393,7 +430,7 @@ In addition, the following HUD animations are triggered by this plugin and may b * `statusspec_playermodels_enabled` - enable custom player models #### Resource Files -Player model configuration is loaded from the `Resource/PlayerModels.res` file. Within the `players` section, players should be configured with an entry consisting of their Steam ID as the key and a group name from the `groups` section as the value. Within the `groups` section, each group should have a section (named with the group name specified in the `players` section above), and model replacements should be specified by entries with the path of the old model from the `models` folder as the key and the path of the new model from the `models` folder as the value. An example of a configured file is given below: +Player model configuration is loaded from the `Resource/PlayerModels.res` file. Within the `players` section, players should be configured with an entry consisting of their Steam ID as the key and a group name from the `groups` section as the value. Within the `groups` section, each group should have a section (named with the group name specified in the `players` section above), and model replacements should be specified by entries with the path of the old model as the key and the path of the new model as the value. An example of a configured file is given below: ``` "models" { @@ -405,15 +442,15 @@ Player model configuration is loaded from the `Resource/PlayerModels.res` file. { "demo-at-heart" { - "player/scout.mdl" "player/demo.mdl" - "player/soldier.mdl" "player/demo.mdl" - "player/pyro.mdl" "player/demo.mdl" - "player/demo.mdl" "player/demo.mdl" - "player/heavy.mdl" "player/demo.mdl" - "player/engineer.mdl" "player/demo.mdl" - "player/medic.mdl" "player/demo.mdl" - "player/sniper.mdl" "player/demo.mdl" - "player/spy.mdl" "player/demo.mdl" + "models/player/scout.mdl" "models/player/demo.mdl" + "models/player/soldier.mdl" "models/player/demo.mdl" + "models/player/pyro.mdl" "models/player/demo.mdl" + "models/player/demo.mdl" "models/player/demo.mdl" + "models/player/heavy.mdl" "models/player/demo.mdl" + "models/player/engineer.mdl" "models/player/demo.mdl" + "models/player/medic.mdl" "models/player/demo.mdl" + "models/player/sniper.mdl" "models/player/demo.mdl" + "models/player/spy.mdl" "models/player/demo.mdl" } } } diff --git a/resource/custommodels.res b/resource/custommodels.res new file mode 100644 index 0000000..5552045 --- /dev/null +++ b/resource/custommodels.res @@ -0,0 +1,3 @@ +"models" +{ +} \ No newline at end of file diff --git a/src/entities.cpp b/src/entities.cpp index dad942d..575c040 100644 --- a/src/entities.cpp +++ b/src/entities.cpp @@ -17,7 +17,6 @@ } int Entities::pCTFPlayer__m_iClass = 0; -int Entities::pCTFPlayer__m_iTeamNum = 0; int Entities::pCTFPlayer__m_nPlayerCond = 0; int Entities::pCTFPlayer___condition_bits = 0; int Entities::pCTFPlayer__m_nPlayerCondEx = 0; @@ -30,19 +29,14 @@ int Entities::pCEconEntity__m_iItemDefinitionIndex = 0; int Entities::pCWeaponMedigun__m_bChargeRelease = 0; int Entities::pCWeaponMedigun__m_nChargeResistType = 0; int Entities::pCWeaponMedigun__m_flChargeLevel = 0; -int Entities::pCTFPlayerResource__m_iHealth[MAX_PLAYERS + 1] = { 0 }; -int Entities::pCTFPlayerResource__m_iMaxHealth[MAX_PLAYERS + 1] = { 0 }; -int Entities::pCTFPlayerResource__m_iMaxBuffedHealth[MAX_PLAYERS + 1] = { 0 }; int Entities::pCTFPlayerResource__m_iKillstreak[MAX_PLAYERS + 1] = { 0 }; int Entities::pCWeaponMedigun__m_bHealing = 0; int Entities::pCWeaponMedigun__m_hHealingTarget = 0; int Entities::pCTFPlayer__m_iKillStreak = 0; int Entities::pCTFGrenadePipebombProjectile__m_iType = 0; -int Entities::pCBaseEntity__m_iTeamNum = 0; bool Entities::PrepareOffsets() { RETRIEVE_OFFSET(pCTFPlayer__m_iClass, GetClassPropOffset("CTFPlayer", pCTFPlayer__m_iClass, 1, "m_iClass")); - RETRIEVE_OFFSET(pCTFPlayer__m_iTeamNum, GetClassPropOffset("CTFPlayer", pCTFPlayer__m_iTeamNum, 1, "m_iTeamNum")); RETRIEVE_OFFSET(pCTFPlayer__m_nPlayerCond, GetClassPropOffset("CTFPlayer", pCTFPlayer__m_nPlayerCond, 1, "m_nPlayerCond")); RETRIEVE_OFFSET(pCTFPlayer___condition_bits, GetClassPropOffset("CTFPlayer", pCTFPlayer___condition_bits, 1, "_condition_bits")); RETRIEVE_OFFSET(pCTFPlayer__m_nPlayerCondEx, GetClassPropOffset("CTFPlayer", pCTFPlayer__m_nPlayerCondEx, 1, "m_nPlayerCondEx")); @@ -59,21 +53,6 @@ bool Entities::PrepareOffsets() { RETRIEVE_OFFSET(pCWeaponMedigun__m_bChargeRelease, GetClassPropOffset("CWeaponMedigun", pCWeaponMedigun__m_bChargeRelease, 1, "m_bChargeRelease")); RETRIEVE_OFFSET(pCWeaponMedigun__m_nChargeResistType, GetClassPropOffset("CWeaponMedigun", pCWeaponMedigun__m_nChargeResistType, 1, "m_nChargeResistType")); RETRIEVE_OFFSET(pCWeaponMedigun__m_flChargeLevel, GetClassPropOffset("CWeaponMedigun", pCWeaponMedigun__m_flChargeLevel, 1, "m_flChargeLevel")); - for (int i = 0; i <= MAX_PLAYERS; i++) { - char *elementName = new char[4]; - sprintf(elementName, "%03i", i); - RETRIEVE_OFFSET(pCTFPlayerResource__m_iHealth[i], GetClassPropOffset("CTFPlayerResource", pCTFPlayerResource__m_iHealth[i], 2, "m_iHealth", elementName)); - } - for (int i = 0; i <= MAX_PLAYERS; i++) { - char *elementName = new char[4]; - sprintf(elementName, "%03i", i); - RETRIEVE_OFFSET(pCTFPlayerResource__m_iMaxHealth[i], GetClassPropOffset("CTFPlayerResource", pCTFPlayerResource__m_iMaxHealth[i], 2, "m_iMaxHealth", elementName)); - } - for (int i = 0; i <= MAX_PLAYERS; i++) { - char *elementName = new char[4]; - sprintf(elementName, "%03i", i); - RETRIEVE_OFFSET(pCTFPlayerResource__m_iMaxBuffedHealth[i], GetClassPropOffset("CTFPlayerResource", pCTFPlayerResource__m_iMaxBuffedHealth[i], 2, "m_iMaxBuffedHealth", elementName)); - } for (int i = 0; i <= MAX_PLAYERS; i++) { char *elementName = new char[4]; sprintf(elementName, "%03i", i); @@ -83,7 +62,6 @@ bool Entities::PrepareOffsets() { RETRIEVE_OFFSET(pCWeaponMedigun__m_hHealingTarget, GetClassPropOffset("CWeaponMedigun", pCWeaponMedigun__m_hHealingTarget, 1, "m_hHealingTarget")); RETRIEVE_OFFSET(pCTFPlayer__m_iKillStreak, GetClassPropOffset("CTFPlayer", pCTFPlayer__m_iKillStreak, 1, "m_iKillStreak")); RETRIEVE_OFFSET(pCTFGrenadePipebombProjectile__m_iType, GetClassPropOffset("CTFGrenadePipebombProjectile", pCTFGrenadePipebombProjectile__m_iType, 1, "m_iType")); - RETRIEVE_OFFSET(pCBaseEntity__m_iTeamNum, GetClassPropOffset("CBaseEntity", pCBaseEntity__m_iTeamNum, 1, "m_iTeamNum")); return true; } diff --git a/src/entities.h b/src/entities.h index 7fde423..d199fc7 100644 --- a/src/entities.h +++ b/src/entities.h @@ -30,7 +30,6 @@ class Entities { public: static int pCTFPlayer__m_iClass; - static int pCTFPlayer__m_iTeamNum; static int pCTFPlayer__m_nPlayerCond; static int pCTFPlayer___condition_bits; static int pCTFPlayer__m_nPlayerCondEx; @@ -43,15 +42,11 @@ class Entities { static int pCWeaponMedigun__m_bChargeRelease; static int pCWeaponMedigun__m_nChargeResistType; static int pCWeaponMedigun__m_flChargeLevel; - static int pCTFPlayerResource__m_iHealth[MAX_PLAYERS + 1]; - static int pCTFPlayerResource__m_iMaxHealth[MAX_PLAYERS + 1]; - static int pCTFPlayerResource__m_iMaxBuffedHealth[MAX_PLAYERS + 1]; static int pCTFPlayerResource__m_iKillstreak[MAX_PLAYERS + 1]; static int pCWeaponMedigun__m_bHealing; static int pCWeaponMedigun__m_hHealingTarget; static int pCTFPlayer__m_iKillStreak; static int pCTFGrenadePipebombProjectile__m_iType; - static int pCBaseEntity__m_iTeamNum; static bool PrepareOffsets(); static bool GetClassPropOffset(const char *className, int &offset, int depth, ...); diff --git a/src/funcs.cpp b/src/funcs.cpp index 84e707d..0736868 100644 --- a/src/funcs.cpp +++ b/src/funcs.cpp @@ -97,6 +97,9 @@ inline SPT_t GetSPTFunc() { #endif } +int Funcs::setModelLastHookRegistered = 0; +std::map> Funcs::setModelHooks; + GLPI_t Funcs::getLocalPlayerIndexOriginal = nullptr; SMI_t Funcs::setModelIndexOriginal = nullptr; SMP_t Funcs::setModelPointerOriginal = nullptr; @@ -150,6 +153,17 @@ int Funcs::AddGlobalHook_C_TFPlayer_GetFOV(C_TFPlayer *instance, fastdelegate::F return SH_ADD_MANUALHOOK(C_TFPlayer_GetFOV, instance, hook, post); } +int Funcs::AddHook_C_BaseEntity_SetModel(std::function hook) { + setModelHooks[++setModelLastHookRegistered] = hook; + + if (setModelHooks.size() > 0) { + AddDetour_C_BaseEntity_SetModelIndex(Detour_C_BaseEntity_SetModelIndex); + AddDetour_C_BaseEntity_SetModelPointer(Detour_C_BaseEntity_SetModelPointer); + } + + return setModelLastHookRegistered; +} + int Funcs::AddHook_IBaseClientDLL_FrameStageNotify(IBaseClientDLL *instance, fastdelegate::FastDelegate1 hook, bool post) { return SH_ADD_HOOK(IBaseClientDLL, FrameStageNotify, instance, hook, post); } @@ -237,6 +251,26 @@ bool Funcs::CallFunc_IVEngineClient_GetPlayerInfo(IVEngineClient *instance, int return SH_CALL(instance, &IVEngineClient::GetPlayerInfo)(ent_num, pinfo); } +void Funcs::Detour_C_BaseEntity_SetModelIndex(C_BaseEntity *instance, void *, int index) { + const model_t *model = Interfaces::pModelInfoClient->GetModel(index); + + for (auto iterator = setModelHooks.begin(); iterator != setModelHooks.end(); ++iterator) { + iterator->second(instance, model); + } + + int newIndex = Interfaces::pModelInfoClient->GetModelIndex(Interfaces::pModelInfoClient->GetModelName(model)); + + Funcs::CallFunc_C_BaseEntity_SetModelIndex(instance, newIndex); +} + +void Funcs::Detour_C_BaseEntity_SetModelPointer(C_BaseEntity *instance, void *, const model_t *pModel) { + for (auto iterator = setModelHooks.begin(); iterator != setModelHooks.end(); ++iterator) { + iterator->second(instance, pModel); + } + + Funcs::CallFunc_C_BaseEntity_SetModelPointer(instance, pModel); +} + bool Funcs::RemoveDetour_GetLocalPlayerIndex() { if (RemoveDetour(GetGLPIFunc())) { getLocalPlayerIndexOriginal = nullptr; @@ -280,6 +314,15 @@ bool Funcs::RemoveHook(int hookID) { return SH_REMOVE_HOOK_ID(hookID); } +void Funcs::RemoveHook_C_BaseEntity_SetModel(int hookID) { + setModelHooks.erase(hookID); + + if (setModelHooks.size() == 0) { + RemoveDetour_C_BaseEntity_SetModelIndex(); + RemoveDetour_C_BaseEntity_SetModelPointer(); + } +} + bool Funcs::Load() { MH_STATUS minHookResult = MH_Initialize(); diff --git a/src/funcs.h b/src/funcs.h index 9249405..00e491d 100644 --- a/src/funcs.h +++ b/src/funcs.h @@ -12,6 +12,7 @@ #include "stdafx.h" +#include #include #define CLIENT_DLL @@ -29,6 +30,8 @@ #include #include +#include "ifaces.h" + using namespace vgui; class C_TFPlayer; @@ -83,10 +86,9 @@ class StatusSpecUnloader: public SourceHook::Impl::UnloadListener class Funcs { public: static bool AddDetour_GetLocalPlayerIndex(GLPI_t detour); - static bool AddDetour_C_BaseEntity_SetModelIndex(SMIH_t detour); - static bool AddDetour_C_BaseEntity_SetModelPointer(SMPH_t detour); static int AddGlobalHook_C_TFPlayer_GetFOV(C_TFPlayer *instance, fastdelegate::FastDelegate0 hook, bool post); + static int AddHook_C_BaseEntity_SetModel(std::function hook); static int AddHook_IBaseClientDLL_FrameStageNotify(IBaseClientDLL *instance, fastdelegate::FastDelegate1 hook, bool post); static int AddHook_IClientMode_DoPostScreenSpaceEffects(IClientMode *instance, fastdelegate::FastDelegate1 hook, bool post); static int AddHook_IGameEventManager2_FireEventClientSide(IGameEventManager2 *instance, fastdelegate::FastDelegate1 hook, bool post); @@ -109,10 +111,9 @@ class Funcs { static bool CallFunc_IVEngineClient_GetPlayerInfo(IVEngineClient *instance, int ent_num, player_info_t *pinfo); static bool RemoveDetour_GetLocalPlayerIndex(); - static bool RemoveDetour_C_BaseEntity_SetModelIndex(); - static bool RemoveDetour_C_BaseEntity_SetModelPointer(); static bool RemoveHook(int hookID); + static void RemoveHook_C_BaseEntity_SetModel(int hookID); static bool Load(); @@ -122,11 +123,23 @@ class Funcs { static bool Unpause(); private: + static int setModelLastHookRegistered; + static std::map> setModelHooks; + static GLPI_t getLocalPlayerIndexOriginal; static SMI_t setModelIndexOriginal; static SMP_t setModelPointerOriginal; static bool AddDetour(void *target, void *detour, void *&original); + static bool AddDetour_C_BaseEntity_SetModelIndex(SMIH_t detour); + static bool AddDetour_C_BaseEntity_SetModelPointer(SMPH_t detour); + + static void __fastcall Detour_C_BaseEntity_SetModelIndex(C_BaseEntity *, void *, int); + static void __fastcall Detour_C_BaseEntity_SetModelPointer(C_BaseEntity *, void *, const model_t *); + + static bool RemoveDetour_C_BaseEntity_SetModelIndex(); + static bool RemoveDetour_C_BaseEntity_SetModelPointer(); + static bool RemoveDetour(void *target); }; \ No newline at end of file diff --git a/src/modules/custommodels.cpp b/src/modules/custommodels.cpp new file mode 100644 index 0000000..1e6bc9e --- /dev/null +++ b/src/modules/custommodels.cpp @@ -0,0 +1,97 @@ +/* +* custommodels.cpp +* StatusSpec project +* +* Copyright (c) 2014 thesupremecommander +* BSD 2-Clause License +* http://opensource.org/licenses/BSD-2-Clause +* +*/ + +#include "custommodels.h" + +CustomModels::CustomModels() { + modelConfig = new KeyValues("models"); + modelConfig->LoadFromFile(Interfaces::pFileSystem, "resource/custommodels.res", "mod"); + setModelHook = 0; + + enabled = new ConVar("statusspec_custommodels_enabled", "0", FCVAR_NONE, "enable custom models", [](IConVar *var, const char *pOldValue, float flOldValue) { g_CustomModels->ToggleEnabled(var, pOldValue, flOldValue); }); + load_replacement_group = new ConCommand("statusspec_custommodels_load_replacement_group", [](const CCommand &command) { g_CustomModels->LoadReplacementGroup(command); }, "load a model replacement group", FCVAR_NONE); + unload_replacement_group = new ConCommand("statusspec_custommodels_unload_replacement_group", [](const CCommand &command) { g_CustomModels->UnloadReplacementGroup(command); }, "unload a model replacement group", FCVAR_NONE); +} + +void CustomModels::SetModelOverride(C_BaseEntity *entity, const model_t *&model) { + const char *modelName = Interfaces::pModelInfoClient->GetModelName(model); + + if (modelReplacements.find(modelName) != modelReplacements.end()) { + model = Interfaces::pModelInfoClient->GetModel(Interfaces::pModelInfoClient->RegisterDynamicModel(modelReplacements[modelName].replacement.c_str(), true)); + } +} + +void CustomModels::LoadReplacementGroup(const CCommand &command) { + if (command.ArgC() < 2) { + Warning("Must specify a replacement group to load!\n"); + return; + } + + const char *group = command.Arg(1); + + KeyValues *replacementsConfig = modelConfig->FindKey(group); + + if (replacementsConfig) { + FOR_EACH_VALUE(replacementsConfig, modelReplacement) { + std::string original = modelReplacement->GetName(); + + if (modelReplacements.find(original) != modelReplacements.end()) { + modelReplacements.erase(original); + } + + modelReplacements[original].group = group; + modelReplacements[original].replacement = modelReplacement->GetString(); + } + } + else { + Warning("Must specify a valid replacement group to load!\n"); + } +} + +void CustomModels::ToggleEnabled(IConVar *var, const char *pOldValue, float flOldValue) { + if (enabled->GetBool()) { + if (!setModelHook) { + setModelHook = Funcs::AddHook_C_BaseEntity_SetModel(std::bind(&CustomModels::SetModelOverride, this, std::placeholders::_1, std::placeholders::_2)); + } + } + else { + if (setModelHook) { + Funcs::RemoveHook_C_BaseEntity_SetModel(setModelHook); + setModelHook = 0; + } + } +} + +void CustomModels::UnloadReplacementGroup(const CCommand &command) { + if (command.ArgC() < 2) { + Warning("Must specify a replacement group to load!\n"); + return; + } + + const char *group = command.Arg(1); + + KeyValues *replacementsConfig = modelConfig->FindKey(group); + + if (replacementsConfig) { + auto iterator = modelReplacements.begin(); + + while (iterator != modelReplacements.end()) { + if (iterator->second.group.compare(group) == 0) { + modelReplacements.erase(iterator++); + } + else { + ++iterator; + } + } + } + else { + Warning("Must specify a valid replacement group to unload!\n"); + } +} \ No newline at end of file diff --git a/src/modules/custommodels.h b/src/modules/custommodels.h new file mode 100644 index 0000000..b39630c --- /dev/null +++ b/src/modules/custommodels.h @@ -0,0 +1,51 @@ +/* +* custommodels.h +* StatusSpec project +* +* Copyright (c) 2014 thesupremecommander +* BSD 2-Clause License +* http://opensource.org/licenses/BSD-2-Clause +* +*/ + +#pragma once + +#include "../stdafx.h" + +#include +#include +#include +#include + +#include "convar.h" +#include "ehandle.h" + +#include "../entities.h" +#include "../funcs.h" +#include "../ifaces.h" +#include "../player.h" + +typedef struct ModelReplacement_s { + std::string group; + std::string replacement; +} ModelReplacement_t; + +class CustomModels { +public: + CustomModels(); +private: + KeyValues *modelConfig; + std::map modelReplacements; + int setModelHook; + + void SetModelOverride(C_BaseEntity *entity, const model_t *&model); + + ConVar *enabled; + ConCommand *load_replacement_group; + ConCommand *unload_replacement_group; + void LoadReplacementGroup(const CCommand &command); + void ToggleEnabled(IConVar *var, const char *pOldValue, float flOldValue); + void UnloadReplacementGroup(const CCommand &command); +}; + +extern CustomModels *g_CustomModels; \ No newline at end of file diff --git a/src/modules/playermodels.cpp b/src/modules/playermodels.cpp index 07746e7..d236749 100644 --- a/src/modules/playermodels.cpp +++ b/src/modules/playermodels.cpp @@ -13,32 +13,16 @@ PlayerModels::PlayerModels() { modelConfig = new KeyValues("models"); modelConfig->LoadFromFile(Interfaces::pFileSystem, "resource/playermodels.res", "mod"); - setModelIndexDetoured = false; - setModelPointerDetoured = false; + setModelHook = 0; enabled = new ConVar("statusspec_playermodels_enabled", "0", FCVAR_NONE, "enable custom player models", [](IConVar *var, const char *pOldValue, float flOldValue) { g_PlayerModels->ToggleEnabled(var, pOldValue, flOldValue); }); } -void PlayerModels::SetModelIndexOverride(C_BaseEntity *instance, int index) { - const model_t *oldModel = Interfaces::pModelInfoClient->GetModel(index); - const model_t *newModel = GetModelOverride(instance, oldModel); - int newIndex = Interfaces::pModelInfoClient->GetModelIndex(Interfaces::pModelInfoClient->GetModelName(newModel)); - - Funcs::CallFunc_C_BaseEntity_SetModelIndex(instance, newIndex); -} - -void PlayerModels::SetModelPointerOverride(C_BaseEntity *instance, const model_t *pModel) { - const model_t *oldModel = pModel; - const model_t *newModel = GetModelOverride(instance, oldModel); - - Funcs::CallFunc_C_BaseEntity_SetModelPointer(instance, newModel); -} - -const model_t *PlayerModels::GetModelOverride(C_BaseEntity *entity, const model_t *model) { +void PlayerModels::SetModelOverride(C_BaseEntity *entity, const model_t *&model) { Player player = entity; if (!player) { - return model; + return; } CSteamID playerSteamID = player.GetSteamID(); @@ -51,51 +35,35 @@ const model_t *PlayerModels::GetModelOverride(C_BaseEntity *entity, const model_ const char *playerGroup = playersConfig->GetString(playerSteamID64.c_str()); if (strcmp(playerGroup, "") == 0) { - return model; + return; } KeyValues *groupsConfig = modelConfig->FindKey("groups"); KeyValues *groupConfig = groupsConfig->FindKey(playerGroup); if (groupConfig == NULL) { - return model; + return; } const char *modelName = Interfaces::pModelInfoClient->GetModelName(model); FOR_EACH_VALUE(groupConfig, replacementModelConfig) { if (strcmp(replacementModelConfig->GetName(), modelName) == 0) { - return Interfaces::pModelInfoClient->GetModel(Interfaces::pModelInfoClient->RegisterDynamicModel(replacementModelConfig->GetString(), true)); + model = Interfaces::pModelInfoClient->GetModel(Interfaces::pModelInfoClient->RegisterDynamicModel(replacementModelConfig->GetString(), true)); } } - - return model; -} - - -inline void __fastcall Detour_C_BaseEntity_SetModelIndex(C_BaseEntity *instance, void *, int index) { - g_PlayerModels->SetModelIndexOverride(instance, index); -} - -inline void __fastcall Detour_C_BaseEntity_SetModelPointer(C_BaseEntity *instance, void *, const model_t *pModel) { - g_PlayerModels->SetModelPointerOverride(instance, pModel); } void PlayerModels::ToggleEnabled(IConVar *var, const char *pOldValue, float flOldValue) { if (enabled->GetBool()) { - if (!setModelIndexDetoured) { - setModelIndexDetoured = Funcs::AddDetour_C_BaseEntity_SetModelIndex(Detour_C_BaseEntity_SetModelIndex); - } - if (!setModelPointerDetoured) { - setModelPointerDetoured = Funcs::AddDetour_C_BaseEntity_SetModelPointer(Detour_C_BaseEntity_SetModelPointer); + if (!setModelHook) { + setModelHook = Funcs::AddHook_C_BaseEntity_SetModel(std::bind(&PlayerModels::SetModelOverride, this, std::placeholders::_1, std::placeholders::_2)); } } else { - if (setModelIndexDetoured) { - setModelIndexDetoured = !Funcs::RemoveDetour_C_BaseEntity_SetModelIndex(); - } - if (setModelPointerDetoured) { - setModelPointerDetoured = !Funcs::RemoveDetour_C_BaseEntity_SetModelPointer(); + if (setModelHook) { + Funcs::RemoveHook_C_BaseEntity_SetModel(setModelHook); + setModelHook = 0; } } } \ No newline at end of file diff --git a/src/modules/playermodels.h b/src/modules/playermodels.h index e77877b..d21c7c0 100644 --- a/src/modules/playermodels.h +++ b/src/modules/playermodels.h @@ -12,6 +12,7 @@ #include "../stdafx.h" +#include #include #include #include @@ -27,15 +28,11 @@ class PlayerModels { public: PlayerModels(); - - void SetModelIndexOverride(C_BaseEntity *instance, int index); - void SetModelPointerOverride(C_BaseEntity *instance, const model_t *pModel); private: KeyValues *modelConfig; - bool setModelIndexDetoured; - bool setModelPointerDetoured; + int setModelHook; - const model_t *GetModelOverride(C_BaseEntity *entity, const model_t *model); + void SetModelOverride(C_BaseEntity *entity, const model_t *&model); ConVar *enabled; void ToggleEnabled(IConVar *var, const char *pOldValue, float flOldValue); diff --git a/src/modules/projectileoutlines.cpp b/src/modules/projectileoutlines.cpp index c7fe431..96b878b 100644 --- a/src/modules/projectileoutlines.cpp +++ b/src/modules/projectileoutlines.cpp @@ -146,7 +146,7 @@ Color ProjectileOutlines::GetGlowColor(IClientEntity *entity) { float blue = 255.0f; float alpha = 255.0f; - TFTeam team = (TFTeam)*MAKE_PTR(int *, entity, Entities::pCBaseEntity__m_iTeamNum); + TFTeam team = (TFTeam)dynamic_cast(entity)->GetTeamNumber(); if (team == TFTeam_Red) { red = colors["red"].color.r(); diff --git a/src/player.cpp b/src/player.cpp index 0b6b527..53b850c 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -323,21 +323,10 @@ CSteamID Player::GetSteamID() const { } TFTeam Player::GetTeam() const { - try { - if (IsValid()) { - int entindex = playerEntity->entindex(); - IGameResources *gameResources = Interfaces::GetGameResources(); - TFTeam team = (TFTeam)gameResources->GetTeam(entindex); - - return team; - } - } - catch (bad_pointer &e) { - Warning(e.what()); + if (IsValid()) { + TFTeam team = (TFTeam)dynamic_cast(playerEntity.Get())->GetTeamNumber(); - if (IsValid()) { - return (TFTeam)*MAKE_PTR(int*, playerEntity.Get(), Entities::pCTFPlayer__m_iTeamNum); - } + return team; } return TFTeam_Unassigned; diff --git a/src/player.h b/src/player.h index 3b9cfc6..a442176 100644 --- a/src/player.h +++ b/src/player.h @@ -16,6 +16,9 @@ #include #include +#define CLIENT_DLL +#include "cbase.h" +#include "c_baseentity.h" #include "icliententity.h" #include "entities.h" diff --git a/src/statusspec.cpp b/src/statusspec.cpp index 6f78f0a..4107bec 100644 --- a/src/statusspec.cpp +++ b/src/statusspec.cpp @@ -13,6 +13,7 @@ AntiFreeze *g_AntiFreeze = nullptr; CameraTools *g_CameraTools = nullptr; CustomMaterials *g_CustomMaterials = nullptr; +CustomModels *g_CustomModels = nullptr; CustomTextures *g_CustomTextures = nullptr; FOVOverride *g_FOVOverride = nullptr; Killstreaks *g_Killstreaks = nullptr; @@ -104,6 +105,7 @@ bool StatusSpecPlugin::Load(CreateInterfaceFn interfaceFactory, CreateInterfaceF g_AntiFreeze = new AntiFreeze(); g_CameraTools = new CameraTools(); g_CustomMaterials = new CustomMaterials(); + g_CustomModels = new CustomModels(); g_CustomTextures = new CustomTextures(); g_FOVOverride = new FOVOverride(); g_Killstreaks = new Killstreaks(); @@ -129,6 +131,7 @@ void StatusSpecPlugin::Unload(void) delete g_AntiFreeze; delete g_CameraTools; delete g_CustomMaterials; + delete g_CustomModels; delete g_CustomTextures; delete g_FOVOverride; delete g_Killstreaks; diff --git a/src/statusspec.h b/src/statusspec.h index 7da6a63..761f57b 100644 --- a/src/statusspec.h +++ b/src/statusspec.h @@ -19,6 +19,7 @@ #include "modules/antifreeze.h" #include "modules/cameratools.h" #include "modules/custommaterials.h" +#include "modules/custommodels.h" #include "modules/customtextures.h" #include "modules/fovoverride.h" #include "modules/killstreaks.h" @@ -35,7 +36,7 @@ #include "modules/teamhealthcomparison.h" #include "modules/teamoverrides.h" -#define PLUGIN_DESC "StatusSpec v0.21.0" +#define PLUGIN_DESC "StatusSpec v0.22.0" void Hook_IBaseClientDLL_FrameStageNotify(ClientFrameStage_t curStage); bool Hook_IClientMode_DoPostScreenSpaceEffects(const CViewSetup *pSetup); diff --git a/statusspec.vpc b/statusspec.vpc index a4d138e..0660e31 100644 --- a/statusspec.vpc +++ b/statusspec.vpc @@ -53,6 +53,7 @@ $Project "StatusSpec" $File "src\modules\antifreeze.cpp" $File "src\modules\cameratools.cpp" $File "src\modules\custommaterials.cpp" + $File "src\modules\custommodels.cpp" $File "src\modules\customtextures.cpp" $File "src\modules\fovoverride.cpp" $File "src\modules\killstreaks.cpp" @@ -94,6 +95,7 @@ $Project "StatusSpec" $File "src\modules\antifreeze.h" $File "src\modules\cameratools.h" $File "src\modules\custommaterials.h" + $File "src\modules\custommodels.h" $File "src\modules\customtextures.h" $File "src\modules\fovoverride.h" $File "src\modules\killstreaks.h"