char MapName[] = "cm01.map"; // The .map file used for this level
char LevelDesc[] = "Dual Turret GP Test Level"; // Description appearing in the game list box
char TechtreeName[] = "Multitek.txt"; // File to use for the tech tree
SDescBlock DescBlock = { Colony, 2, 12, false }; // Important level details (GameType, NumPlayers, MaxTechLevel, UnitOnlyMission)
int InitProc()
{
Unit unit;
Unit guardPost;
// Make sure graphics look right
Player[0].GoEden();
Player[1].GoEden();
// Create a guard post
TethysGame::CreateUnit(guardPost, mapGuardPost, LOCATION(100,40), 0, mapLaser, 0);
// Create a power plant to power the guard post
TethysGame::CreateUnit(unit, mapTokamak, LOCATION(90, 40), 0, mapNone, 0);
// Get the address of the flags
int* flagsAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120 + 0x44);
// Set the dual turret flag
*flagsAddress |= 0x20;
// Create some enemy units
TethysGame::CreateUnit(unit, mapLynx, LOCATION(110,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(116,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(122,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
// A refrence unit for you
TethysGame::CreateUnit(unit, mapLynx, LOCATION(80,40), 0, mapLaser, 0);
// Center the view on the action
Player[0].CenterViewOn(110, 40);
return 1; // return 1 if OK; 0 on failure
}
Once you have figured how to make the turrets LOOK dual, i'll put it into the Renegades campaign.That might be a bit of a problem because the GP graphics are different from the vehicle graphics, and no dual turret graphics exist for the GPs.
Come to think of it, for all the games where I have dug into their datafiles for graphics and such, OP2 has very little waste / unused data when compared with other games. (There are only a handful of graphics in OP2_ART that aren't actually used in game in some manner).Isn't it possible to expand OP2_ART with new items ?
004CFFB0 .rdata User Unit:GuardPost.vtbl
004CFFB0 >0042D700 <Outpost2.Unit:GuardPost.GetUnitTypeInfo()>
004CFFC8 0042D450 <Outpost2.Unit:GuardPost.DrawUnit()>
#include <memory.h>
// DualTurretGPvftbl
void* dualTurretGPvftbl[65];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, 65*4);
// Update desired function pointers here
// Return the new virtual function table
return dualTurretGPvftbl;
}
void* SetupDualTurretGP();
void* dualTurretGPvftbl = SetupDualTurretGP();
// Get the address of the virtual function table pointer
int* vtblPtrAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120);
// Set the new virtual function table
*vtblPtrAddress = (int)dualTurretGPvftbl;
int InitProc()
{
Unit unit;
Unit guardPost;
void* dualTurretGPvftbl = SetupDualTurretGP();
// Make sure graphics look right
Player[0].GoEden();
Player[1].GoEden();
// Create a guard post
TethysGame::CreateUnit(guardPost, mapGuardPost, LOCATION(100,40), 0, mapLaser, 0);
// Create a power plant to power the guard post
TethysGame::CreateUnit(unit, mapTokamak, LOCATION(90, 40), 0, mapNone, 0);
// Get the address of the flags
int* flagsAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120 + 0x44);
// Set the dual turret flag
*flagsAddress |= 0x20;
// Get the address of the virtual function table pointer
int* vtblPtrAddress = (int*)(*(int*)(0x0054F848) + guardPost.unitID*120);
// Set the new virtual function table
*vtblPtrAddress = (int)dualTurretGPvftbl;
// Create some enemy units
TethysGame::CreateUnit(unit, mapLynx, LOCATION(110,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(116,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(122,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
// A refrence unit for you
TethysGame::CreateUnit(unit, mapLynx, LOCATION(80,40), 0, mapLaser, 0);
// Center the view on the action
Player[0].CenterViewOn(110, 40);
return 1; // return 1 if OK; 0 on failure
}
// Replacement DrawUnit function
__declspec(naked) void DualTurretGP_DrawUnit(void *graphicsObject)
{
__asm RETN 4;
}
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DualTurretGP_DrawUnit;
#include <memory.h>
// Replacement DrawUnit function
__declspec(naked) void DualTurretGP_DrawUnit(void *graphicsObject)
{
__asm RETN 4;
}
// DualTurretGPvftbl
void* dualTurretGPvftbl[65];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, 65*4);
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DualTurretGP_DrawUnit;
// Return the new virtual function table
return dualTurretGPvftbl;
}
// Replacement DrawUnit function
void __stdcall DualTurretGP_DrawUnit(void *graphicsObject)
{
}
// Replacement DrawUnit function
void __stdcall DualTurretGP_DrawUnit(void *graphicsObject)
{
void *thisGuardPost;
__asm MOV thisGuardPost, ECX;
}
Isn't it possible to expand OP2_ART with new items ?
And besides .. i'm talking about Renegades: i can have some weapons unavailable, and use those graphics (alter them) to display a dual-turret.
00484CB0 > 8>SUB ESP,10 ; Function: Unit:Tank.DrawUnit(???* ???)
Unit:Tank.GetTurretGraphic(Unit *baseUnit, char rotation)
004855C0 > . 8>MOV EDX,DWORD PTR SS:[ESP+8] ; Function: Unit:Tank:Lynx.GetTurretGraphic(Unit *baseUnit, char rotation) [Returns info in global variables]
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
// Include important header files needed to interface with Outpost 2
#include "..\Outpost2DLL\Outpost2DLL.h"
void* SetupDualTurretGP();
void SetDualTurretFlag(int unitIndex);
void SetUnitVtbl(int unitIndex, void* newVtbl);
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr);
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr);
void __stdcall GetTankTurretAnimationIndex(void* baseUnit, int rotation);
void __stdcall DrawDualTurretGPUnit(void *graphicsObject);
// Required level exports
char MapName[] = "cm01.map"; // The .map file used for this level
char LevelDesc[] = "Dual Turret GP Test Level"; // Description appearing in the game list box
char TechtreeName[] = "Multitek.txt"; // File to use for the tech tree
SDescBlock DescBlock = { Colony, 2, 12, false }; // Important level details (GameType, NumPlayers, MaxTechLevel, UnitOnlyMission)
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls((HMODULE)hModule);
}
return TRUE;
}
int InitProc()
{
Unit unit;
Unit guardPost;
void* dualTurretGPvftbl = SetupDualTurretGP();
// Make sure graphics look right
Player[0].GoEden();
Player[1].GoEden();
// Create a guard post
TethysGame::CreateUnit(guardPost, mapGuardPost, LOCATION(100,40), 0, mapLaser, 0);
// Create a power plant to power the guard post
TethysGame::CreateUnit(unit, mapTokamak, LOCATION(90, 40), 0, mapNone, 0);
// Set the Dual Turret flag on the guard post
SetDualTurretFlag(guardPost.unitID);
// Set a new vtbl for the Dual Turret GP
//SetUnitVtbl(guardPost.unitID, dualTurretGPvftbl);
// Create some enemy units
TethysGame::CreateUnit(unit, mapLynx, LOCATION(110,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(116,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(122,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
// A refrence unit for you
TethysGame::CreateUnit(unit, mapLynx, LOCATION(100,20), 0, mapLaser, 0);
// Create two Panthers, one dual turret, one not
TethysGame::CreateUnit(unit, mapPanther, LOCATION(104, 34), 0, mapLaser, 2);
TethysGame::CreateUnit(unit, mapPanther, LOCATION(104, 34), 0, mapLaser, 2);
// Modify the second panther to be dual turret
SetDualTurretFlag(unit.unitID);
// Modify the vtbl (for all panthers) to handle dual turret graphics
SetUnitVtblEntry(unit.unitID, 5, &GetTankTurretAnimationIndex);
// Center the view on the action
Player[0].CenterViewOn(110, 40);
TethysGame::SetDaylightEverywhere(true);
return 1; // return 1 if OK; 0 on failure
}
void AIProc()
{
}
void __cdecl GetSaveRegions(struct BufferDesc &bufDesc)
{
bufDesc.bufferStart = 0; // Pointer to a buffer that needs to be saved
bufDesc.length = 0; // sizeof(buffer)
}
int StatusProc()
{
return 0; // must return 0
}
SCRIPT_API void NoResponseToTrigger()
{
}
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <memory.h>
void* SetupDualTurretGP();
void SetDualTurretFlag(int unitIndex);
void SetUnitVtbl(int unitIndex, void* newVtbl);
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr);
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr);
void __stdcall GetTankTurretAnimationIndex(void* baseUnit, int rotation);
void __stdcall DrawDualTurretGPUnit(void *graphicsObject);
// New DualTurretGP virtual function table
const int GPVtblSize = 65;
void* dualTurretGPvftbl[GPVtblSize];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, sizeof(dualTurretGPvftbl));
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DrawDualTurretGPUnit;
// Return the new virtual function table
return dualTurretGPvftbl;
}
void SetDualTurretFlag(int unitIndex)
{
int* unitArray = (int*)0x54F848;
char* unitData = (char*)(((char*)(*unitArray)) + 120 * unitIndex); // Get the Unit address
// Set the Dual Turret flag
*(int*)(unitData + 0x44) |= 0x20;
}
void SetUnitVtbl(int unitIndex, void* newVtbl)
{
int* unitArray = (int*)0x54F848;
int* unitData = (int*)(((char*)(*unitArray)) + 120 * unitIndex); // Get the Unit address
// Set the new vtbl
*unitData = (int)newVtbl;
}
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr)
{
int* unitArray = (int*)0x54F848;
int* unitVtbl = (*(int**)(((char*)(*unitArray)) + 120 * unitIndex)); // Load the Unit vtbl pointer
// Modify the unit vtbl entry
SetVtblEntry(unitVtbl, entryIndex, funcAddr);
}
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr)
{
DWORD oldProtection;
DWORD dummyProtection;
void* entryAddress = &(((void**)vtblAddr)[entryIndex]);
// Unprotect the memory
if (VirtualProtect(entryAddress, 4, PAGE_READWRITE, &oldProtection))
{
// Modify the vtbl entry
((int*)vtblAddr)[entryIndex] = (int)funcAddr;
// Reprotect the memory
VirtualProtect(entryAddress, 4, oldProtection, &dummyProtection);
return true;
}
return false;
}
// Replacement member function for Unit:Tank
void __stdcall GetTankTurretAnimationIndex(void* weaponUnit, int rotation)
{
// Unit values
void* baseUnit;
__asm MOV [baseUnit], ECX // Store hidden "this" pointer
int weaponType;
int flags;
int chasisRotation;
int rotationIndex;
// UnitTypeInfo values
int* unitTypeInfoPtrArray = (int*)0x4E1348;
void* weaponUnitTypeInfo;
void* getTurretAnimationFuncAddr;
// Graphics values
void* graphics = (void*)0x4EFD68;
void* getOffsetFuncAddr = (void*)0x404F00;
// Globals
int* animationOffsetX = (int*)0x4EA7BC;
int* animationOffsetY = (int*)0x4EA7C0;
int offsetX;
int offsetY;
// Get the weapon type
weaponType = *(short*)((char*)baseUnit + 0x24);
// Get the flags
flags = *(int*)((char*)baseUnit + 0x44);
// Get the chasis rotation
chasisRotation = *((char*)baseUnit + 0x1C);
rotationIndex = (chasisRotation + 8) / 16; // Round to nearest multiple of 16 (22.5 degrees)
// Get the weapon UnitTypeInfo
weaponUnitTypeInfo = (void*)unitTypeInfoPtrArray[weaponType];
// Get the weapon type Get<Chasis>TurretAnimationIndex function address
if (flags & 0x20) // Check for double fire rate
{
// Get the address of GetTigerTurretAnimationIndex
getTurretAnimationFuncAddr = (void*)(*(int*)((char*)(*(int*)weaponUnitTypeInfo) + 0x20));
}
else
{
// Get the address of GetPantherTurretAnimationIndex
getTurretAnimationFuncAddr = (void*)(*(int*)((char*)(*(int*)weaponUnitTypeInfo) + 0x24));
}
// Get the animation index (set into a global)
__asm
{
MOV EAX, rotation
MOV EDX, baseUnit
MOV ECX, weaponUnitTypeInfo // this* [UnitTypeInfo:Weapon*]
PUSH EAX // Arg2 - int rotation
PUSH EDX // Arg1 - Unit* baseUnit
CALL [getTurretAnimationFuncAddr] // UnitTypeInfo.Get<Chasis>TurretAnimation(Unit* baseUnit, int rotation)
}
// Get the animation offsets of the chasis
__asm
{
// Get the Chasis animation index
MOV EDX, rotationIndex
PUSH EDX // Arg1 - int rotationIndex
MOV ECX, baseUnit // this* [Unit*]
MOV EAX, [ECX] // Unit.vtbl
CALL [EAX + 0x80] // Unit.GetChasisAnimationIndex(int rotationIndex)
// Get the animation offset of the chasis
PUSH 0 // Arg2 - int frameIndex
PUSH EAX // Arg1 - int animationIndex
MOV ECX, graphics // this* [Graphics*]
CALL [getOffsetFuncAddr] // Graphics.GetAnimationFrameOffsets(int animationIndex)
// Store to locals
MOVSX ECX, AL
MOVSX EAX, AH
MOV [offsetX], ECX
MOV [offsetY], EAX
}
// Update the global variable with the offsets
*animationOffsetX += offsetX;
*animationOffsetY += offsetY;
}
// Replacement member function for Unit:GuardPost
void __stdcall DrawDualTurretGPUnit(void *graphicsObject)
{
void *thisGuardPost;
__asm MOV thisGuardPost, ECX;
}
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
#include <windows.h>
// Include important header files needed to interface with Outpost 2
#include <Outpost2DLL.h>
void* SetupDualTurretGP();
void SetDualTurretFlag(int unitIndex);
void ClearDualTurretFlag(int unitIndex);
void SetUnitVtbl(int unitIndex, void* newVtbl);
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr);
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr);
void __stdcall GetTankTurretAnimationIndex(void* baseUnit, int rotation);
void __stdcall DrawDualTurretGPUnit(void *graphicsObject);
// Required level exports
char MapName[] = "cm01.map"; // The .map file used for this level
char LevelDesc[] = "Dual Turret GP Test Level"; // Description appearing in the game list box
char TechtreeName[] = "Multitek.txt"; // File to use for the tech tree
SDescBlock DescBlock = { Colony, 2, 12, false }; // Important level details (GameType, NumPlayers, MaxTechLevel, UnitOnlyMission)
BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls((HMODULE)hModule);
SetVtblEntry((int*)0x004D0880, 5, &GetTankTurretAnimationIndex); // Patch lynx vtbl entry to new func addr
SetVtblEntry((int*)0x004D0938, 5, &GetTankTurretAnimationIndex); // Patch panther vtbl entry to new func addr
SetVtblEntry((int*)0x004D05C0, 5, &GetTankTurretAnimationIndex); // Patch tiger vtbl entry to new func addr
} else if (ul_reason_for_call == DLL_PROCESS_DETACH) {
SetVtblEntry((int*)0x004D0880, 5, (void*)0x004855C0); // Patch lynx vtbl entry to original value
SetVtblEntry((int*)0x004D0938, 5, (void*)0x004857A0); // Patch panther vtbl entry to original value
SetVtblEntry((int*)0x004D05C0, 5, (void*)0x00485950); // Patch tiger vtbl entry to original value
}
return TRUE;
}
int InitProc()
{
Unit unit;
Unit guardPost;
void* dualTurretGPvftbl = SetupDualTurretGP();
// Make sure graphics look right
Player[0].GoEden();
Player[1].GoEden();
// Create a guard post
TethysGame::CreateUnit(guardPost, mapGuardPost, LOCATION(100,40), 0, mapLaser, 0);
// Create a power plant to power the guard post
TethysGame::CreateUnit(unit, mapTokamak, LOCATION(90, 40), 0, mapNone, 0);
// Set the Dual Turret flag on the guard post
SetDualTurretFlag(guardPost.unitID);
// Set a new vtbl for the Dual Turret GP
//SetUnitVtbl(guardPost.unitID, dualTurretGPvftbl);
// Create some enemy units
TethysGame::CreateUnit(unit, mapLynx, LOCATION(110,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(116,40), 1, mapLaser, 0);
unit.DoMove(LOCATION(100,40)); // Send it in
TethysGame::CreateUnit(unit, mapLynx, LOCATION(122,40), 1, mapThorsHammer, 0);
// Modify the third lynx to be dual turret
SetDualTurretFlag(unit.unitID);
unit.DoMove(LOCATION(100,40)); // Send it in
// A not really refrence unit for you
TethysGame::CreateUnit(unit, mapTiger, LOCATION(100,20), 0, mapLaser, 0);
// Modify the tiger to be single turret
ClearDualTurretFlag(unit.unitID);
// Create two Panthers, one dual turret, one not
TethysGame::CreateUnit(unit, mapPanther, LOCATION(104, 34), 0, mapThorsHammer, 2);
TethysGame::CreateUnit(unit, mapPanther, LOCATION(104, 34), 0, mapThorsHammer, 2);
// Modify the second panther to be dual turret
SetDualTurretFlag(unit.unitID);
// Center the view on the action
Player[0].CenterViewOn(110, 40);
TethysGame::SetDaylightEverywhere(true);
return 1; // return 1 if OK; 0 on failure
}
void AIProc()
{
}
void __cdecl GetSaveRegions(struct BufferDesc &bufDesc)
{
bufDesc.bufferStart = 0; // Pointer to a buffer that needs to be saved
bufDesc.length = 0; // sizeof(buffer)
}
int StatusProc()
{
return 0; // must return 0
}
SCRIPT_API void NoResponseToTrigger()
{
}
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <memory.h>
void* SetupDualTurretGP();
void SetDualTurretFlag(int unitIndex, bool foo);
void ClearDualTurretFlag(int unitIndex);
void SetUnitVtbl(int unitIndex, void* newVtbl);
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr);
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr);
void __stdcall GetTankTurretAnimationIndex(void* baseUnit, int rotation);
void __stdcall DrawDualTurretGPUnit(void *graphicsObject);
// New DualTurretGP virtual function table
const int GPVtblSize = 65;
void* dualTurretGPvftbl[GPVtblSize];
void* SetupDualTurretGP()
{
// Copy the vtbl we want to edit
memcpy(&dualTurretGPvftbl, (void*)0x004CFFB0, sizeof(dualTurretGPvftbl));
// Update the DrawUnit function pointer
dualTurretGPvftbl[6] = &DrawDualTurretGPUnit;
// Return the new virtual function table
return dualTurretGPvftbl;
}
void SetDualTurretFlag(int unitIndex)
{
int* unitArray = (int*)0x54F848;
char* unitData = (char*)(((char*)(*unitArray)) + 120 * unitIndex); // Get the Unit address
*(int*)(unitData + 0x44) |= 0x20;
}
void ClearDualTurretFlag(int unitIndex)
{
int* unitArray = (int*)0x54F848;
char* unitData = (char*)(((char*)(*unitArray)) + 120 * unitIndex); // Get the Unit address
*(int*)(unitData + 0x44) &= ~0x20;
}
void SetUnitVtbl(int unitIndex, void* newVtbl)
{
int* unitArray = (int*)0x54F848;
int* unitData = (int*)(((char*)(*unitArray)) + 120 * unitIndex); // Get the Unit address
// Set the new vtbl
*unitData = (int)newVtbl;
}
void SetUnitVtblEntry(int unitIndex, int entryIndex, void* funcAddr)
{
int* unitArray = (int*)0x54F848;
int* unitVtbl = (*(int**)(((char*)(*unitArray)) + 120 * unitIndex)); // Load the Unit vtbl pointer
// Modify the unit vtbl entry
SetVtblEntry(unitVtbl, entryIndex, funcAddr);
}
bool SetVtblEntry(void* vtblAddr, int entryIndex, void* funcAddr)
{
DWORD oldProtection;
DWORD dummyProtection;
void* entryAddress = &(((void**)vtblAddr)[entryIndex]);
// Unprotect the memory
if (VirtualProtect(entryAddress, 4, PAGE_READWRITE, &oldProtection))
{
// Modify the vtbl entry
((int*)vtblAddr)[entryIndex] = (int)funcAddr;
// Reprotect the memory
VirtualProtect(entryAddress, 4, oldProtection, &dummyProtection);
return true;
}
return false;
}
// Replacement member function for Unit:Tank
void __stdcall GetTankTurretAnimationIndex(void* weaponUnit, int rotation)
{
// Unit values
void* baseUnit;
__asm MOV [baseUnit], ECX // Store hidden "this" pointer
int weaponType;
int flags;
int chasisRotation;
int rotationIndex;
// UnitTypeInfo values
int* unitTypeInfoPtrArray = (int*)0x4E1348;
void* weaponUnitTypeInfo;
void* getTurretAnimationFuncAddr;
// Graphics values
void* graphics = (void*)0x4EFD68;
void* getOffsetFuncAddr = (void*)0x404F00;
// Globals
int* animationOffsetX = (int*)0x4EA7BC;
int* animationOffsetY = (int*)0x4EA7C0;
int offsetX;
int offsetY;
// Get the weapon type
weaponType = *(short*)((char*)baseUnit + 0x24);
// Get the flags
flags = *(int*)((char*)baseUnit + 0x44);
// Get the chasis rotation
chasisRotation = *((char*)baseUnit + 0x1C);
rotationIndex = (chasisRotation + 8) / 16; // Round to nearest multiple of 16 (22.5 degrees)
// Get the weapon UnitTypeInfo
weaponUnitTypeInfo = (void*)unitTypeInfoPtrArray[weaponType];
// Get the weapon type Get<Chasis>TurretAnimationIndex function address
if (flags & 0x20) // Check for double fire rate
{
// Get the address of GetTigerTurretAnimationIndex
getTurretAnimationFuncAddr = (void*)(*(int*)((char*)(*(int*)weaponUnitTypeInfo) + 0x20));
}
else
{
// Get the address of GetPantherTurretAnimationIndex
getTurretAnimationFuncAddr = (void*)(*(int*)((char*)(*(int*)weaponUnitTypeInfo) + 0x24));
}
// Get the animation index (set into a global)
__asm
{
MOV EAX, rotation
MOV EDX, baseUnit
MOV ECX, weaponUnitTypeInfo // this* [UnitTypeInfo:Weapon*]
PUSH EAX // Arg2 - int rotation
PUSH EDX // Arg1 - Unit* baseUnit
CALL [getTurretAnimationFuncAddr] // UnitTypeInfo.Get<Chasis>TurretAnimation(Unit* baseUnit, int rotation)
}
// Get the animation offsets of the chasis
__asm
{
// Get the Chasis animation index
MOV EDX, rotationIndex
PUSH EDX // Arg1 - int rotationIndex
MOV ECX, baseUnit // this* [Unit*]
MOV EAX, [ECX] // Unit.vtbl
CALL [EAX + 0x80] // Unit.GetChasisAnimationIndex(int rotationIndex)
// Get the animation offset of the chasis
PUSH 0 // Arg2 - int frameIndex
PUSH EAX // Arg1 - int animationIndex
MOV ECX, graphics // this* [Graphics*]
CALL [getOffsetFuncAddr] // Graphics.GetAnimationFrameOffsets(int animationIndex)
// Store to locals
MOVSX ECX, AL
MOVSX EAX, AH
MOV [offsetX], ECX
MOV [offsetY], EAX
}
// Update the global variable with the offsets
*animationOffsetX += offsetX;
*animationOffsetY += offsetY;
}
// Replacement member function for Unit:GuardPost
void __stdcall DrawDualTurretGPUnit(void *graphicsObject)
{
void *thisGuardPost;
__asm MOV thisGuardPost, ECX;
}