Hooman, how do I make meteors instantly appear, rather than have the 10-mark delay?
Something like this: ;)
// Virtual member functions
virtual void OnClickDetailPane()
{
LogMessage("OnClickDetailPane()");
// Create a disaster
OP2ForcedExport::Unit* disasterUnit;
Point &pos = mouseCommandFilter.mapMousePos;
disasterUnit = sheet.CreateUnit(disasterType, pos.x, pos.y, 6, (map_id)disasterStrength, 0, true);
disasterUnit->flags |= 0xC000;
disasterUnit->actionTimer = 1;
};
This was ripped out of a project I had lying around. The most relevant parts are where it sets the two flags bits for the disaster already being warned, and sets the timer down to 1. If those bits are clear when the timer counts down to 0, then you get those advanced warning messages (possibly), it sets the warned bit, and the timer resets and start to count down again. I remember the flags field is at offset 0x44 in the Unit class, and the timer is at offset 0x3C. The unit class is 120 bytes long, so if you know the right unit index, and the array base address, those fields are at: base + 120*index + offset (0x3C/0x44).
Of course that still leaves finding the unit index. An Enum might do it, but the usual disaster creation functions don't return a handle to the created disaster. I cheated a bit in the above code by calling an internal unit creation function directly, which didn't give just the unit index, but an actual pointer to the unit. I'm not entirely sure how to suggest getting the unit pointer with simpler methods, but I know I once wrote an Enum class that could fetch off units that you normally didn't get to see, like beacons. I know the code is floating around out there somewhere, but I can't seem to find it at the moment. Maybe check out ZZJ's project in the SVN, as I think it had it in there, or an adaptation of it.
Yeah, from that project I mentioned in the SVN:
UnitEnum.h
-------------
#ifndef UNITENUM
#define UNITENUM
// Include important header files needed to interface with Outpost 2
#include <Outpost2DLL.h>
class UnitEnum
{
public:
UnitEnum(int playerNum = -1, map_id type = mapAny);
int GetNext(Unit &unit);
private:
int currentUnit;
int unitType;
int playerNum;
};
#endif
UnitEnum.cpp
---------------
#include "UnitEnum.h"
// Note: You should avoid creating or deleting Units while traversing the list
// as there is no guarantee if such units will be returned or not
int UnitEnum::GetNext(Unit &unit)
{
int lastUsedUnitIndex = *(int*)0x0054F838; // Max number of Unit records that were ever allocated
for (;;)
{
// Increment unit index (and skip over entry 0)
currentUnit++;
// Make sure we haven't walked off the end of the list
if (currentUnit > lastUsedUnitIndex)
return false; // No more Units to check
// Set the unit ID
unit.SetId(currentUnit);
if ((playerNum == -1) || (unit.OwnerID() == playerNum))
{
if ((unitType == mapAny) || (unit.GetType() == unitType))
{
if (unit.IsLive())
{
// Live Unit found with desired owner and type
return true;
}
}
}
}
}
UnitEnum::UnitEnum(int plrNum, enum map_id type)
{
currentUnit = 0;
unitType = type;
playerNum = plrNum;
}
You might use it something like this:
Export void TriggerCallbackFunction()
{
// Create the Meteor
TethysGame::SetMeteor(x, y, size);
UnitEnum meteorEnum(PlayerAll, mapMeteor) // Note: PlayerAll = -1
Unit meteor;
while (meteorEnum.GetNext(meteor))
{
// Just assume this is the right meteor, but we should probably put some check here
char* meteorUnit = (char*)(*(int*)0x54F848 + meteor.unitID * 120); // Get base address of unit array, and index into it
*(int*)(meteorUnit + 0x44) |= 0xC000; // Set disaster first+second warn flags
*(int*)(meteorUnit + 0x3C) = 1; // Set action timer down to 1
}
}
Err, isn't it obvious from the file/function names? (Ignore the first post).
Or you could try some nifty assembly for a better solution. Maybe just call that function I used directly, and do it like I did it in the first post.
Function prototype (for understanding purposes only):
Unit* CreateUnit(map_id unitType, int pixelX, int pixelY, int creatorIndex, map_id cargo, int unitIndex, bool bCenterInTile); // 0x004467C0 Returns 0 (NULL) when out of unit records
...
// Globals
extern Sheet sheet; // 0x0055B780
void __declspec(naked) CreateMeteor(int x, int y, int size)
{
__asm
{
push 1 // Arg7 - bCenterInTile = true
push 0 // Arg6 - unitIndex = 0
mov eax, [esp+0x8 + 0xC] // Param3 - int size
push eax // Arg5 - size
push 6 // Arg4 - creatorIndex = Gaia
mov eax, [esp+0x10 + 8]// Param2 - int y
shl eax, 5 // convert tileY to pixelY (y*32 = y * (2^5) = y << 5)
push eax // Arg3 - pixelY
mov eax, [esp+0x14 + 4]// Param1 - int x
shl eax, 5 // convert tileX to pixelX (x*32 = x * (2^5) = x << 5)
push eax // Arg2 - pixelX
push mapMeteor // Arg1 - unitType
mov ecx, 0x0055B780 // "this" pointer for the Sheet object
mov eax, 0x004467C0 // Load address of CreateUnit function
call eax // Call CreateUnit (return value is in eax, which is a unit pointer)
or dword ptr [eax + 0x44], 0xC000 // Set DisasterWarn flags 1 & 2
mov dword ptr [eax + 0x3C], 1 // Set timer down to 1 tick
ret // Return, leaving the caller to clean the stack (__cdecl)
}
}
Edit: Almost forgot to remove the 10 mark delay
Edit: Tested, and fixed 3 bugs. The parameters were read with reversed offsets, the parameter offsets were not accounting for the arguments being pushed onto the stack, and the memory writes defaulted to a single byte instead of a full dword.
Edit: Fixed another bug, which somehow didn't crash during initial testing. The __cdecl calling convention, which is the default for non member functions (i.e. "C" functions), requires the caller to clean the stack, not the callee.
I found this function in Swarm. There were no comments...
void Meteor(LOCATION Location, int Offset)
{
Unit meteor;
TethysGame::CreateUnit(meteor, mapMeteor, Location, 6, (map_id)3, 0);
int* meteorUnit = (int*)((*(int*)0x54F848) + meteor.unitID * 120);
int* meteorUnitFlags = (int*)((char*)meteorUnit + 0x44);
int* meteorUnitTimer = (int*)((char*)meteorUnit + 0x3C);
*meteorUnitFlags |= 0xC000;
*meteorUnitTimer = Offset;
}
I was using it to create meteor showers in for() loops like this (i think offset is the time until it hits in milliseconds?):
Meteor(x.Location(), 3000);
Presumably Hooman gave me the code to do this some time last year or so.