And link rot strikes again. Let me see if I can get a URL for you.
https://forum.outpost2.net/index.php?action=dlattach;topic=4409.0;attach=246
Hoping this is the version you want -- shows as last modified by Sirbomber in December, 2008.
While I'm at it, there's also this image that appears to go with it:
(https://forum.outpost2.net/index.php?action=dlattach;topic=4925.0;attach=329)
Tried to make a multiplayer scenario to implement as close to the revised morale system as I could figure out how to. The results are summarized in the comments. I probably did something really dumb; I'll try again later.
void AIProc()
{
if (TethysGame::UsesMorale())
{
//only calculate the below stuff every so often, to avoid excessive load on the system.
//This is causing the below code to NEVER fire, so apparently there's something I don't understand here.
//if ((TethysGame::Tick() & 0x1F) == 0)
//{
//The below code is causing the host's game to instantly exit, and causing the other player to report "CHEATED GAME!".
for (int i = 0; i < 2; i++)
{
int moralePoints = 0;
if (Player[i].HasTechnology(3301)) //DIRT
moralePoints += 2;
if (Player[i].HasTechnology(3303)) //Med Centers
moralePoints += 2;
if (Player[i].HasTechnology(3304)) //Nursery
moralePoints += 1;
if (Player[i].HasTechnology(3305)) //University
moralePoints += 1;
if (Player[i].HasTechnology(3306)) //Rec Facility
moralePoints += 4;
if (Player[i].HasTechnology(5108)) //Forum
moralePoints += 1;
if (Player[i].HasTechnology(5101)) //Consumer Goods
moralePoints += 1;
if (Player[i].HasTechnology(03302)) //GORF
moralePoints += 1;
//out of 12 possible points, you need 11 or more for Excellent, 7 or more for Good, and 3 or more for Fair morale.
//If your morale exceeds what you're allowed to get, you get brought down to the previous level, forcibly.
if (moralePoints < 3)
{
if (Player[i].MoraleLevel() == moraleOK || Player[i].MoraleLevel() == moraleGood || Player[i].MoraleLevel() == moraleGreat)
{
TethysGame::ForceMoralePoor(i);
TethysGame::FreeMoraleLevel(i);
}
}
else if (moralePoints < 7)
{
if (Player[i].MoraleLevel() == moraleGood || Player[i].MoraleLevel() == moraleGreat)
{
TethysGame::ForceMoraleOK(i);
TethysGame::FreeMoraleLevel(i);
}
}
else if (moralePoints < 11)
{
if (Player[i].MoraleLevel() == moraleGreat)
{
TethysGame::ForceMoraleGood(i);
TethysGame::FreeMoraleLevel(i);
}
}
}
//}
}
}
The ForceMoraleXYZ() functions won't do what you want it to do... they lock the player's morale at that level, as in, it won't go up or down. Calling any morale functions past mark 0 (with the exception of FreeMoraleLevel, and only if it's called for all players) will cause the CHEATED GAME! behavior you're seeing.
I'd suggest breaking your morale hack into its own function (for example, Export void MoraleHack()), which you then call on a repeating time trigger initialized in InitProc, rather than... whatever you're trying to do with counting ticks.
Something like this:Export int InitProc(){ ... CreateTimeTrigger(1, 0, 5000, "MoraleHack"); ...}
Export void MoraleHack{ ...}
Edit: Ugh, that looked better before the forum removed all my return characters and whitespace.
Triggers and AIProc aren't called every tick. The condition to check is:
00403249 CMP AL,3 ; Check if ((tick MOD 4) != 3)
0040324B JNZ Outpost2.00403506 ; -> Return
Try changing your code to:
if ((TethysGame::Tick() & 0x1F) == 3)
Edit: Or use Sirbomber's suggestion. That might be more natural for Outpost 2 level code.
If Morale is set after game start (after the tick has incremented past 0), you'll get "CHEATED GAME!". The only thing you can do with morale after game start is to call FreeMoraleLevel. This is part of more general code to set GameOpt. Only a handful are allowed after tick 0 without causing the "CHEATED GAME!" message to appear. Details: Forced Exports (renamed OP2Internal) (https://forum.outpost2.net/index.php/topic,4249.msg64375.html#msg64375)
You can try setting the memory locations directly if you want to bypass the "CHEATED GAME!" message. That project should contain details for the memory addresses.
I'll figure out triggers sometime tomorrow. Scheduling a check every 32 ticks or so should do what I was attempting with the AI proc thing.
According to DataStructure Player.txt, the offset of the morale number within the player is 0x040, while the morale level is 0x034, so does this work?
//after doing stuff to determine maxMorale
int* morale = (&Player[i]) + 0x40;
int* moraleLevel = (&Player[i]) + 0x34;
if (*morale > maxMorale)
{
*morale = maxMorale;
if(*morale >= 90)
*moraleLevel = 0;
else if(*morale >= 70)
*moraleLevel = 1;
else if(*morale >= 45)
*moraleLevel = 2;
else if(*morale >= 25)
*moraleLevel = 3;
else *moraleLevel = 4;
}
Having done stuff with more safe/modern computing environments for a long time now, I feel uncomfortable just pointing at some memory and saying "I'm pretty sure this is data type X. Now go execute some code that could to terrible, horrible things if I'm wrong."
-------
Edit: The above didn't work for a couple reasons. First, I had to explicitly cast the pointer as being a (int*). Second, it appears that Player[] does not point to the structures documented in DataStructure Player.txt, so I had to move the pointer. After dealing with that, though, it seems to be working. Also, getting the repeating trigger to work wasn't too hard, though referring to a function name by a string whose contents happen to match the name of the function kind of freaks me out. What is this, DOS batch file programming?
I believe you can use a building enumerator to find the unit record for the mine. I'm not sure if you want the mine building, or the mining beacon. Try either of:
PlayerBuildingEnum(Player6, mapMiningBeacon); // Gaia
PlayerBuildingEnum(playerNum, mapCommonOreMine);
PlayerBuildingEnum(playerNum, mapRareOreMine);
From there, you could use a bit of HFL style memory hacking to access the value at the offset you want.
I don't think I found beacons through PlayerBuildingEnum, but I could be wrong.
Try it with PlayerUnitEnum(Player6, mapMiningBeacon)
For getting beacon data, there is a solution I use in my own HFL library that you can find on my GitHub.
Here's the key parts:
struct BeaconData
{
int numTruckLoadsSoFar;
int barYield;
int variant;
char oreType; // [0 = common, 1 = rare]
char unknown1;
char unknown2;
char surveyedBy; // [player bit vector]
};
and to get the data for the struct:
BeaconData* p = (BeaconData*)((int)(*unitArray) + (unitID*120) + 0x58);
Credit goes to some awesome poster from 2011 or so.
Yes, that should work, provided you have a way of finding the correct unitID / unitIndex.
If you really need to, you can implement your own enumerator class. A few people have done that before. If you can't use an existing enumerator as a base, then you can always try looping over the unit array manually and determine the unit type. The game usually determines the unit type in a bit of a roundabout way:
unitRecord.GetUnitTypeInfo().unitType; // enum map_id
The GetUnitTypeInfo() method is the first method in the virtual function table. The unitType field is at offset 0x4 in the UnitTypeInfo class (the first field after the virtual function table pointer).
Crow!,
I just posted a beta release of op2ext that allows checking which console modules are loaded.
https://github.com/OutpostUniverse/op2ext/releases/tag/ver2.2.0-beta
You should be able to check if your module is present by calling the following from within the mission's InitProc function:
if (!IsModuleLoaded("TestModule")) {
... WARN USER / CLOSE MAP / CHANGE MAP BEHAVIOUR ...
}
Let me know if you need any help setting up.
Thanks,
Brett
OK, the module loaded check in that build of op2ext seems to be working how I want it to for Ohana - the mission already had two different initial setups coded in, depending on a single bool I had been setting to a different value depending on whether I was building the vanilla or PhaseShift version. So, initializing it with:
bool isPhaseShift = IsModuleLoaded("PhaseShift");
Is, in my local tests, correctly building the bases and applying new morale and mining rules according to whether the PhaseShift module is loaded.
It is possible I'd like to have a more understandable error message given to the player for a colony game if I have it refuse to load due to not having PhaseShift active, but I'm not sure if that necessarily has to involve op2ext.
As for the audio issue, I have confirmed that packing the replacement sound into voices.vol placed in the mod's directory causes static when the game attempts to play the sound.
Crow!,
quick update on op2ext. We are removing 2 functions from the public API:
OP2EXT_API bool IsConsoleModuleLoaded(const char* moduleName)
OP2EXT_API bool IsIniModuleLoaded(const char* moduleName)
Instead of using these functions, you would just call:
OP2EXT_API bool IsModuleLoaded(const char* moduleName)
We believe the 2 functions being removed were a mistake to add to the API in the first place. A user should not care if the module is loaded via console or Ini file inclusion. Since the update to the API is still pretty fresh, we thought it was best to correct instead of deprecating in this case.
Hopefully this doesn't cause you any development concerns. If so, please let us know.
-Brett