Outpost Universe Forums

Projects & Development => Outpost 2 Programming & Development => Topic started by: Vagabond on January 23, 2016, 11:05:57 AM

Title: How to create compound triggers for failure conditions
Post by: Vagabond on January 23, 2016, 11:05:57 AM
I'm digging into the SDK and putting together a colony map. Using Visual Studio 2015, the SDK compiled the test map without any fiddeling, which was awesome. I am comfortable programming in C#, but this is my first time using C++. I suspect this is a C++ syntax question, but not sure?

I added a failure condition to the map so if you do not have a Command Center you lose. My question is, how can I add an and statement to the failure trigger so it checks all of the following conditions before failing the scenario?

 * You do not have a constructed CC
 * You do not have a CC in a ConVec
 * You do not have a CC kit in the structure factory

Below is the code I have so far.

Code: [Select]
	
Trigger trigMissingCC = CreateCountTrigger(1, 0, -1, mapCommandCenter, mapNone, 1, cmpLower, "NoResponseToTrigger");
Trigger trigMissingConVecCC = CreateCountTrigger(1, 0, -1, mapConVec, mapCommandCenter, 1, cmpLower, "NoResponseToTrigger");
Trigger trigMissingStructFactoryCCKit = CreateCountTrigger(1, 0, -1, mapStructureFactory, mapCommandCenter, 1, cmpLower, "NoResponseToTrigger");

CreateFailureCondition(1, 0, trigMissingCC, "NoCommandCenter");
Title: Re: How to create compound triggers for failure conditions
Post by: Vagabond on January 23, 2016, 11:30:45 AM
Okay,

I read through more of SirBombers tutorials on mapmaking and they answered half of my question. You can use a SetTrigger to combine triggers. So the triggers are combined now into one SetTrigger.

I think I'm using one of the triggers incorrectly though. If all command centers on the map are destroyed and there are no command center kits on the map, the game will not fail until you blow up the structure factory. I'm guessing the CountTrigger I'm using for checking the Structure Factory contents is incorrect?

Perhaps the solution is to use the CreateKitTrigger function instead, but I don't see how to use this function to check if the kit doesn't exist.

CreateKitTrigger syntax:
Code: [Select]
CreateKitTrigger(enabled, one-time, Player Number, Structure Kit Type, Minimum Amount Needed, "Trigger Function");

Current Code I'm using:
Code: [Select]
	
Trigger trigMissingCC = CreateCountTrigger(1, 0, -1, mapCommandCenter, mapNone, 1, cmpLower, "NoResponseToTrigger");
Trigger trigMissingConVecCC = CreateCountTrigger(1, 0, -1, mapConVec, mapCommandCenter, 1, cmpLower, "NoResponseToTrigger");
Trigger trigMissingStructFactoryCCKit = CreateCountTrigger(1, 0, -1, mapStructureFactory, mapCommandCenter, 1, cmpLower, "NoResponseToTrigger");
Trigger setTriggerCCs = CreateSetTrigger(1, 1, 3, 3, "NoResponseToTrigger", trigMissingCC, trigMissingConVecCC, trigMissingStructFactoryCCKit);

CreateFailureCondition(1, 0, setTriggerCCs, "NoCommandCenter");
Title: Re: How to create compound triggers for failure conditions
Post by: dave_erald on January 23, 2016, 11:33:50 AM
You'll have to look up something about putting some of these triggers in AiProc or something.

One of those main.cpp Procs holds information that Op2 will check every so many game ticks, useful when you want the game to keep an eye on special units (don't forget about tagging units as special units) and whether they are alive or dead.
Title: Re: How to create compound triggers for failure conditions
Post by: Hooman on January 24, 2016, 07:09:58 AM
I think you have the right idea so far. The SetTrigger is the way to combine triggers. I think you're right about the Structure Factory, as factories have 6 cargo bays, whereas the parameter to CreateCountTrigger is for a single cargo item such as in a Convec or Cargo Truck. I don't believe it works on factories.

I've noticed you've set the second parameter to 0, so the trigger can fire multiple times. I believe that is correct. Some of those conditions can be met at various times during play, but not all at once, so you'll want to triggers to track updates.

I see the KitTrigger doesn't take a comparison parameter to specify less-than. You're right, I don't know how to fix that off the top of my head.

Was briefly thinking maybe another SetTrigger can invert it's meaning? Must have zero underlying triggers met. But I think the closest you can get is zero or more conditions active. Not quite what you want.

Another approach, is to manually scan for the failure condition. If you detect failure, create a time trigger with a minimal delay and tie failure to that.
Title: Re: How to create compound triggers for failure conditions
Post by: Sirbomber on January 24, 2016, 10:08:20 AM
As far as I'm aware there is no way to set a comparison type on a Kit Trigger - it's always "player has at least this many", which is useless for your purposes.  I think Hooman is right, and you will have to "fake" it by checking in AIProc.  Something like this (untested):

Code: [Select]
Trigger t1, t2, t3, t4;

int InitProc()
{
   ...
   t1 = (CC count trigger)
   t2 = (ConVec kit count trigger)
   t3 = CreateTimeTrigger(0, 0, 10, "NoResponseToTrigger");    // Note that this trigger is disabled by default.
   t4 = CreateSetTrigger(1, 1, 3, 3, "NoResponseToTrigger", t1, t2, t3);
   CreateFailureCondition(1, 0, t4, "Ignored");
   ...
}

void AIProc()
{
   // Not possible with vanilla SDK.  Requires HFL/UnitEx for GetFactoryCargo.
   UnitEx nextSF;
   bool foundKit = false;
   PlayerBuildEnum FindAllSFs(-1, mapStructureFactory);
   while (FindAllSFs.GetNext(nextSF) && !foundKit)
   {
      for (int i = 0; i < 6; i++)
      {
         if (nextSF.GetFactoryCargo(i) == mapCommandCenter)
         {
             foundKit = true;
             break;
          }
      }

   if (!foundKit)
   {
      t3.Enable();
   }
   else
   {
      t3.Disable();
   }
}
Title: Re: How to create compound triggers for failure conditions
Post by: Hooman on January 24, 2016, 10:33:33 AM
Nice job Sirbomber.

I noticed you used -1 for the Player. I'm assuming a cooperative mindset here. In this case, would that only fail the current player if no other player has a CC kit?

That actually looks like good code to extract into a function "HasCommandCenterKit(int playerNum)". You could replace the inner if body with "return true", place a "return false" after the loop, and get rid of the "foundKit" variable. (Details provided as food for thought for less experienced people reading this thread, I'm sure you know how to do this).


It might also make sense to somehow create custom triggers in an API somewhere. It'd be useful to have a way to invert the truth of a trigger. I wonder how messy that could be to implement.
Title: Re: How to create compound triggers for failure conditions
Post by: Sirbomber on January 24, 2016, 10:45:28 AM
This actually brings up a good point, Hooman.  Yes, I was kind of assuming this was a co-op game (though looking at the code Vagabond posted he uses -1 as well for his other triggers so maybe my assumption isn't that far off).  However, it's a little-known fact that OP2 doesn't handle mid-game failure in multiplayer very well.  If one person triggers a failure condition, the game ends like normal for them...  but their defeat doesn't get sent across the network, so for all other players in the game it's treated as if the defeated player's connection was suddenly terminated.

In layman's terms, if you fail one player, the other 3 players get a "lost contact with player X" popup that they have to deal with.  It's jarring, to say the least.

Regarding a custom trigger library, I like the idea, but question how useful it would actually be.  I feel like the triggers OP2 provides usually are enough, and if I ever have to do anything "custom" it's usually pretty far out there.  The only exception I can think of is "a specific unit dies", which is a reasonable enough thing to look for and kind of surprising there's no existing trigger for that.
Title: Re: How to create compound triggers for failure conditions
Post by: Hooman on January 24, 2016, 10:51:38 AM
That is very jarring. What do the built in levels do when a player dies? I assumed they failed, but I never noticed such disconnect boxes before.
Title: Re: How to create compound triggers for failure conditions
Post by: Sirbomber on January 24, 2016, 11:02:40 AM
Nothing.  "Defeated" players get to remain in the game until only one player has an active Command Center remaining.  It's called a "LastOneStanding" trigger for a reason.  :P
Title: Re: How to create compound triggers for failure conditions
Post by: Vagabond on January 24, 2016, 01:10:28 PM
Hey everyone, thanks for the help so far!

I am playing with a colony map. Using player = -1 in the triggers was a copy/paste error on my part. Good to know -1 stands for all players. Since I don't have an AI colony, it was functioning properly and I hadn't noticed.

I found Hacker's Function Library (HFL) on the SVN and the GetFactoryCargo in the UnitEx.cpp.

SirBomber, thank you for posting the solution. I think you mean PlayerBuildingEnum instead of PlayerBuildEnum.

I'm reading that HFL is considered a buggy extension to the SDK on some of the pages and in the HFL Readme. When we say buggy, how buggy are we talking about? Well that is kind of a vague question, but what I mean is are people actively using it to create maps without too much trouble or should it be avoided?
Title: Re: How to create compound triggers for failure conditions
Post by: Sirbomber on January 24, 2016, 01:28:08 PM
I use it and don't often have too much trouble with it.  Not sure what about it is supposed to be so buggy.
Title: Re: How to create compound triggers for failure conditions
Post by: Vagabond on January 25, 2016, 02:59:37 PM
I managed to include HFL into SDK solution without trouble. I also learned the C++ compiler reads code sequentially, requiring me to pay attention to the order Functions are declared.

I had trouble using the triggers. I think the timeTrigger watching for a CC kit in the Structure Factory would fire when the scenario was loaded, so later on you would fail if your CC was destroyed even if you had a backup kit the Structure Factory. I just pulled the SetTrigger out and started manually checking for each failure condition.

Okay, the current version is below which I have tested and seems to be working correctly. Thank you for everyone's help.

Code: [Select]

Trigger trigFailureWaitTime;

int InitProc()
{
        HFLInit();

        trigFailureWaitTime = CreateTimeTrigger(0, 0, 100, "NoResponseToTrigger");
        CreateFailureCondition(1, 0, trigFailureWaitTime, "Ignored");
}

bool BuildingConstructed(int player, map_id buildingType)
{
        Unit structure;
PlayerBuildingEnum findAllStructures(player, buildingType);

while (findAllStructures.GetNext(structure))
{
return true;
}

return false;
}

bool KitInConVec(int player, map_id structureKitType)
{
Unit vehicle;
PlayerUnitEnum findAllVehicles(player);

while (findAllVehicles.GetNext(vehicle))
{
if (vehicle.GetType() == mapConVec && vehicle.GetCargo() == structureKitType)
{
return true;
}
}

return false;
}

bool KitInStructFactory(int player, map_id structureKitType)
{
// Below Requires HFL/UnitEx for GetFactoryCargo.
UnitEx nextSF;
PlayerBuildingEnum findAllSFs(player, mapStructureFactory);

while (findAllSFs.GetNext(nextSF))
{
for (int i = 0; i < 6; i++)
{
if (nextSF.GetFactoryCargo(i) == structureKitType)
{
return true;
}
}
}

  return false;
}

bool BuildingOrKitAvailable(int player, map_id buildingType)
{
return BuildingConstructed(player, buildingType) ||
KitInConVec(player, buildingType) ||
KitInStructFactory(player, buildingType);
}

bool CheckIfGameFailed()
{
if (!BuildingOrKitAvailable(0, mapCommandCenter))
{
return true;
}

if (!BuildingOrKitAvailable(0, mapStructureFactory))
{
if (!BuildingOrKitAvailable(0, mapSpaceport))
{
return true;
}

//TODO: Check if Phonix module not researched. If not, and no AdvLab, fail game.
//if (!hasBuildingOrKitAvailable(0, mapAdvancedLab))
//{
// return true;
//}
}

return false;
}

void AIProc()
{
if (CheckIfGameFailed())
{
TrigFailureWaitTime.Enable();
}
}
Title: Re: How to create compound triggers for failure conditions
Post by: Hooman on January 25, 2016, 04:51:07 PM
Very nice job. That was quite readable code.

I noticed what appears to be a few oversights:
In KitInConVec you used a hardcoded mapCommandCenter rather than the parameter structureKitType.
In BuildingOrKitAvailable you use hardcoded 0s rather than the parameter player.

I kind of like how you're thinking of other possible no-win situations. That can become quite a long complicated tail to chase.
Title: Re: How to create compound triggers for failure conditions
Post by: Vagabond on January 25, 2016, 07:01:26 PM
@Hooman,

Thank you for reviewing. I edited the post to fix the errors you saw. Also fixed a couple other minor things I just noticed like leaving out the HFLInit() call in InitProc() and messing up camel case on a function.

I want the player to be able to lose the scenario without just quitting.
Title: Re: How to create compound triggers for failure conditions
Post by: Hooman on January 26, 2016, 09:26:03 AM
Ahh yes, leaving out HFLInit can cause some funny things to happen.

Side note: There are varying opinions on how acronyms should be capitalized in camel case. Is it "HTTPRequest", or "HttpRequest"? Some people suggest treating acronyms like words, where only the first letter is capitalized. I find that's actually the more readable way, but there's a lot of weight behind capitalizing each letter like is normally the case. For example, AIProc. Consistency is probably the most important thing though.