Skip to content

Commit

Permalink
Merge branch 'release/0.22.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
thesupremecommander committed Nov 25, 2014
2 parents 24a424e + e84840e commit c51c7da
Show file tree
Hide file tree
Showing 18 changed files with 319 additions and 139 deletions.
31 changes: 0 additions & 31 deletions BUILD.md

This file was deleted.

31 changes: 31 additions & 0 deletions DEVELOP.md
Original file line number Diff line number Diff line change
@@ -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.
61 changes: 49 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -236,7 +242,7 @@ The configuration file for the freeze info HUD is `Resource/UI/FreezeInfo.res`.
* `statusspec_custommaterials_unload_replacement_group <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"
{
Expand All @@ -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 <group>` - load a model replacement group
* `statusspec_custommodels_unload_replacement_group <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*

Expand All @@ -259,7 +296,7 @@ Player model configuration is loaded from the `Resource/CustomMaterials.res` fil
* `statusspec_customtextures_unload_replacement_group <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"
{
Expand Down Expand Up @@ -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"
{
Expand All @@ -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"
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions resource/custommodels.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"models"
{
}
22 changes: 0 additions & 22 deletions src/entities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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"));
Expand All @@ -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);
Expand All @@ -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;
}
Expand Down
5 changes: 0 additions & 5 deletions src/entities.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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, ...);
Expand Down
43 changes: 43 additions & 0 deletions src/funcs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ inline SPT_t GetSPTFunc() {
#endif
}

int Funcs::setModelLastHookRegistered = 0;
std::map<int, std::function<void(C_BaseEntity *, const model_t *&)>> Funcs::setModelHooks;

GLPI_t Funcs::getLocalPlayerIndexOriginal = nullptr;
SMI_t Funcs::setModelIndexOriginal = nullptr;
SMP_t Funcs::setModelPointerOriginal = nullptr;
Expand Down Expand Up @@ -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<void(C_BaseEntity *, const model_t *&)> 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<ClientFrameStage_t> hook, bool post) {
return SH_ADD_HOOK(IBaseClientDLL, FrameStageNotify, instance, hook, post);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
Loading

0 comments on commit c51c7da

Please sign in to comment.