Author Topic: Instant Disasters  (Read 2516 times)

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Instant Disasters
« on: February 20, 2009, 01:40:26 AM »
Quote
Hooman, how do I make meteors instantly appear, rather than have the 10-mark delay?


Something like this:  ;)
Code: [Select]
	// 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.
 

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Instant Disasters
« Reply #1 on: February 22, 2009, 02:46:41 AM »
Yeah, from that project I mentioned in the SVN:

UnitEnum.h
-------------
Code: [Select]
#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
---------------
Code: [Select]

#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:
Code: [Select]
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
  }
}
« Last Edit: February 22, 2009, 02:49:00 AM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3238
Instant Disasters
« Reply #2 on: February 24, 2009, 11:07:50 AM »
Okay, so, where would I put it, though?
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Instant Disasters
« Reply #3 on: February 25, 2009, 03:52:07 PM »
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):
Code: [Select]
  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


Code: [Select]
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.
 
« Last Edit: February 28, 2009, 02:46:41 AM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3238
Instant Disasters
« Reply #4 on: February 25, 2009, 03:54:59 PM »
Quote
Err, isn't it obvious from the file/function names? (Ignore the first post).
My fault for being vague.  Do I just stick that code in once, or do I have to copy/paste it before every time I want to use a meteor?
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Instant Disasters
« Reply #5 on: February 25, 2009, 04:00:40 PM »
The enum code is once in it's own seperate files. The callback code is just an example of how it might be done. If you want more meteors controlled by more callbacks, than duplicate it, or put it in some kind of function. Note that I didn't declare x and y in the callback in my second post. I leave it up to you to fill in that detail.


Edit: I should also point out that my second post has the unfortunate effect of modifying all meteors that it finds so they all appear instantly. The assembly solution in my third post will only change the meteor being created now.
« Last Edit: February 25, 2009, 04:02:15 PM by Hooman »

Offline Sirbomber

  • Hero Member
  • *****
  • Posts: 3238
Instant Disasters
« Reply #6 on: February 27, 2009, 03:22:39 PM »
Gaaaah, this stuff is way too over my head...  That or I haven't been getting enough sleep.
I should probably just kill your Command Center with (unit).DoDeath anyways; it's less spammy and less hard to do.  But it also has less of an "LOL N00B" effect which is important to have.

Decisions, decisions...

Edit: Anyways, if you wouldn't mind getting back to me as soon as possible, that'd be great.  I don't mean to rush you but I'm eager to be done with "Bomber's Big Blast" and move onto some new projects (and finish old ones, like Scout Rush 2) and this (and the music pause/unpause code of DETH) is the only thing stopping me from releasing the DLL.  Hmm, this really reads more like a PM, doesn't it?
« Last Edit: February 27, 2009, 03:24:19 PM by Sirbomber »
"As usual, colonist opinion is split between those who think the plague is a good idea, and those who are dying from it." - Outpost Evening Star

Outpost 2 Coding 101 Tutorials

Offline Mcshay

  • Administrator
  • Sr. Member
  • *****
  • Posts: 404
Instant Disasters
« Reply #7 on: February 27, 2009, 09:36:36 PM »
I found this function in Swarm. There were no comments...

Code: [Select]
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?):

Code: [Select]
Meteor(x.Location(), 3000);


Presumably Hooman gave me the code to do this some time last year or so.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Instant Disasters
« Reply #8 on: February 27, 2009, 10:51:25 PM »
Ahh, excellent McShay, that should work perfectly, and is much simpler looking. I'd sort of forgot about just abusing CreateUnit in odd ways like that. Btw, that (map_id)3 is the disaster strength.

Although, why do you have a 3000 in there, instead of something like 1? Did you want the meteor to take longer to land but not give a warning or something? That "offset" value, is the number of ticks before the meteor appears. Looks like it will wait 30 marks with your example.


Edit: P.S. the style of that code doesn't look like mine. I don't generally name parameters with that casing, and I usually use seperate x and y parameters rather than using LOCATION structs. It might have come from someone else.

Edit: Just fixed 3 bugs in some of the assembly I posted above.
Edit: Just fixed yet another bug in that code. Now works in two seperate DLLs, so hopefully it's good now.
 
« Last Edit: February 28, 2009, 02:47:44 AM by Hooman »

Offline Mcshay

  • Administrator
  • Sr. Member
  • *****
  • Posts: 404
Instant Disasters
« Reply #9 on: February 28, 2009, 10:51:40 AM »
Ah, ok so the offset is in ticks. I really didn't know where the code came from or what it did.