pftq.com
Welcome! Please Login or Register!
December 05, 2020, 04:38:37 PM
Home Login Register
pftq Forums  |  Game Design  |  Age of Empires III  |  RMS function or something else? 0 Members and 1 Guest are viewing this topic. « previous next »
Latest News!Three Tiers as a Book(Oct 16, 2020)
Pages: [1] Print
Author Topic: RMS function or something else?  (Read 2712 times)
Neuron
Member
*****
Offline Offline

Posts: 30

« on: May 28, 2013, 02:28:02 AM »

Hi. I have an XS scripting question. I've seen one of River_God's maps contains a weird command with a syntax similar to that of functions. It's something like this:

Code:
string SSmercCall(int ssGroup=0, int ssNumber=0){
if (ssGroup==1) {
if (ssNumber==1) return("MercJaeger");
if (ssNumber==2) return("MercHighlander");
if (ssNumber==3) return("MercHackapell");
if (ssNumber==4) return("MercBlackRider");}
if (ssGroup==2) {
if (ssNumber==1) return("MercBarbaryCorsair");
if (ssNumber==2) return("MercFusilier");
if (ssNumber==3) return("MercStradiot");
if (ssNumber==4) return("MercManchu");}
if (ssGroup==3) {
if (ssNumber==1) return("ypMercIronTroop");
if (ssNumber==2) return("ypMercArsonist");
if (ssNumber==3) return("ypMercJatLancer");
if (ssNumber==4) return("ypMercYojimbo");}
if (ssGroup==4) {
if (ssNumber==1) return("ypWokouBlindMonk");
if (ssNumber==2) return("ypWokouPirate");
if (ssNumber==3) return("ypWokouWanderingHorseman");
if (ssNumber==4) return("ypWokouWaywardRonin");}
if (ssGroup==5) {
if (ssNumber==1) return("SaloonOutlawPistol");
if (ssNumber==2) return("SaloonOutlawRider");
if (ssNumber==3) return("SaloonOutlawRifleman");
if (ssNumber==4) return("SaloonPirate");}
if (ssGroup==6) return("Miner");
return("");
}
I know that in RMS functions are initiated with void before the name, so I'm not sure what this code does. It looks like a function and it's called like a function used as a variable somewhere else in the script:

Code:
for(i=1; <=cNumberNonGaiaPlayers){
SpawnSpot(i, SSarea1ID, 0.25, 0.5, SSmercCall(Ri1,Var1), "Explorer");
....etc
}

Is it possible to create functions without void before their name in RMS?

Normally, I would ask the map creator, but I couldn't find any River_God's email.
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4140

WWW
« Reply #1 on: June 01, 2013, 08:55:30 PM »

void is just the type of function - in programming, we treat void as "action", like a function that just does something but doesn't return a value.

Having string in front means the function returns a string (not the return lines).  So it's usable like a string variable when you call the function.
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #2 on: June 02, 2013, 08:27:33 AM »

Thanks for the answer, again. Btw, I'm not trying to copy anything from your maps or River_God's (I make my own map designs with my own code), I'm just trying to learn how to use functions and (scripted) triggers better. I already know all the basic XS scripting stuff for maps, so I'm trying to get to more advanced stuff, like the methods you and River_God used in your maps.

These things were not listed or explained in any tutorial, and no XS scripting glossary includes how you can use functions in more advanced ways. I suppose this is a subset of C/C++? Can you use other syntax and methods from general programming in XS scripting? Besides loops, conditional loops, functions, variables and the limited maths that the XS compiler understands, are there more ways to use general programming in RM scripts?

I guess what I'm asking is if the game compiler is customised to accept only some general code and the specific XS code.

I also noticed that you used functions to inject code with a SendChat trigger dynamically in a map script, so that some stuff can happen conditionally on how the game goes. The weird thing is that the commands you injected in the map script are not listed in the map dumps as being acceptable for the compiler (for example, trPlayerResourceCount). So, I guess you can use more commands than those listed in a map dump, which was supposed to show a list of all rm/tr/xs commands the compiler can process and all the variable types.
« Last Edit: June 02, 2013, 08:35:07 AM by Neuron » Logged
pftq
Administrator
*****
Offline Offline



Posts: 4140

WWW
« Reply #3 on: June 02, 2013, 01:42:28 PM »

I think it is a subset of C/C++.  I'm not sure.  Best way is to just try it.  Syntax stuff I think is about the same (function declarations etc).

The SendChat injections send code to another layer of the game code that the rm/tr/xs stuff translates to.  So we're bypassing that part.  I explained it here more detailed:
https://www.pftq.com/forums/index.php?topic=1755.msg4369#msg4369
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #4 on: June 04, 2013, 04:00:12 AM »

Yeah, I read that topic. I understood how code injection works but I didn't understand why the syntax is this way:

Quote
rmSetTriggerConditionParamInt("Count", "\"+trQuestVarGet(\"QVpopLimit\")+\"");

You are basically tricking the game moving across the environments when you use "+code+".  The backslashes are there so the quotes get carried into the XS Environment.  It then becomes:

1. ("\"+code+\"")

So, normally a RM function syntax for triggers is like this:

rmSomethingTriggerSomething("Parameter", "Value");

Using the example in that topic, the value for the Count parameter would be

\"+trQuestVarGet(\"QVpopLimit\")+\"

Why is one backslash inside the quotes and another one outside the quotes here?

And, btw, I've noticed that the syntax for scripted triggers varies to some extent. Here's a sample from a map coded by Ensemble programmers (Silk Road):

Quote
   rmCreateTrigger("GuardianDeath"+triggerCounter);
      rmSwitchToTrigger(rmTriggerID("GuardianDeath"+triggerCounter));
      rmSetTriggerPriority(3);
      rmSetTriggerActive(true);
      rmSetTriggerRunImmediately(true);
      rmSetTriggerLoop(false);
        
      rmAddTriggerCondition("Units In Area");
     rmSetTriggerConditionParamInt( "DstObject", rmGetUnitPlaced(socketID, triggerCounter), false );
      rmSetTriggerConditionParam("Player", "0", false);
      rmSetTriggerConditionParam("UnitType", guardianType, false);
      rmSetTriggerConditionParam("Dist", guardianDistance, false);
      rmSetTriggerConditionParam("Op", "<=", false);
      rmSetTriggerConditionParam("Count", "0", false);
      
      rmAddTriggerEffect("Unit Action Suspend");
      rmSetTriggerEffectParamInt("SrcObject", rmGetUnitPlaced(socketID, triggerCounter), false);
      rmSetTriggerEffectParam("ActionName", "AutoConvert", false);
      rmSetTriggerEffectParam("Suspend", "False", false);
      
      rmCreateTrigger("DisableAutoconvert"+triggerCounter);
      rmSwitchToTrigger(rmTriggerID("DisableAutoconvert"+triggerCounter));
      rmSetTriggerPriority(3);
      rmSetTriggerActive(true);
      rmSetTriggerRunImmediately(true);
      rmSetTriggerLoop(false);
        
      rmAddTriggerCondition("Always");
      
      rmAddTriggerEffect("Unit Action Suspend");
      
      rmSetTriggerEffectParamInt("SrcObject", rmGetUnitPlaced(socketID, triggerCounter), false);
      
      rmSetTriggerEffectParam("ActionName", "AutoConvert", false);
      rmSetTriggerEffectParam("Suspend", "True", false);
      
      tempCounter = tempCounter + 2;

And here's another example of scripted triggers from one of your maps (Cannonfodder):

Quote
void UnitsInArea(int centeritem=0, int xplayer=0, string xunit="", string xdist="0", string xop="==", string xcount="0") {
      rmAddTriggerCondition("Units in Area");
      rmSetTriggerConditionParamInt("DstObject", centeritem);
      rmSetTriggerConditionParam("Player", ""+xplayer);
      rmSetTriggerConditionParam("UnitType", xunit);
      rmSetTriggerConditionParam("Dist", xdist);
      rmSetTriggerConditionParam("Op", xop);
      rmSetTriggerConditionParam("Count", xcount);
}

Notice how Ensemble coders use a boolean variable after each value for a parameter and how the value for each parameter is inserted as string, in quotes. Does that mean that the compiler can accept both types of syntax?


This syntax difference can be tricky. I've been trying to make a trigger work on a map, but so far with no result. The compiler accepts the code as valid, but the trigger doesn't do what is expected -- make a trading post spam a unit at a particular point, if a player controls the socker (aka, player has built a trading post on the socket).

I tried using both types of syntax (with and without a boolean variable, with and without quotes) and the one from the Ensemble map is accepted by the compiler but with no effect. Here's the code:

// Trade route def and placement

   int tradeRoute1ID = rmCreateTradeRoute();
   int socketID=rmCreateObjectDef("sockets to dock Trade Posts");
   rmSetObjectDefTradeRouteID(socketID, tradeRoute1ID);
   rmAddObjectDefItem(socketID, "SocketTradeRoute", 1, 0.0);
   rmSetObjectDefAllowOverlap(socketID, true);
   rmAddObjectDefToClass(socketID, rmClassID("socketClass"));
   rmSetObjectDefMinDistance(socketID, 0.0);
   rmSetObjectDefMaxDistance(socketID, 8.0);
  
   rmAddTradeRouteWaypoint(tradeRoute1ID, .48, .5);
   rmAddTradeRouteWaypoint(tradeRoute1ID, .45, .5);

   bool placedTradeRoute1 = rmBuildTradeRoute(tradeRoute1ID, "dirt");
   if(placedTradeRoute1 == false) rmEchoError("Failed to place trade route 1");
  
   vector socketLoc1 = rmGetTradeRouteWayPoint(tradeRoute1ID, 0.5);
   rmPlaceObjectDefAtPoint(socketID, 0, socketLoc1);  

// Locate the socket

   int tpSocketSelect = rmGetUnitPlaced(socketID, 0);
   vector tpSocketPosition = rmGetUnitPosition(rmGetUnitPlacedOfPlayer(socketID, 0));

// Trading post triggers

      rmCreateTrigger("TP revenue");
      rmSwitchToTrigger(rmTriggerID("TP revenue"));
      rmSetTriggerActive(true);
      rmSetTriggerLoop(true);

      rmAddTriggerCondition("Player Controls Socket");
      rmSetTriggerConditionParam("PlayerID", "1", true);
      rmSetTriggerConditionParam("Socket", "tpSocketSelect", true);

      rmAddTriggerEffect("Send Chat");
      rmSetTriggerEffectParam("PlayerID", "1", true);
      rmSetTriggerEffectParam("Message", "The Inca Temple are happy to ally with you and provide you with mercenary support!");
      

      rmAddTriggerCondition("Player Population");
      rmSetTriggerConditionParam("PlayerID", "1", true);
      rmSetTriggerConditionParam("Op", "<", true);
      rmSetTriggerConditionParam("Count", "200", true);


      rmAddTriggerEffect("Unit Create");
      rmSetTriggerEffectParam("Player", "1", true);
      rmSetTriggerEffectParam("ProtoName", "OutlawMusketeer", true);
      rmSetTriggerEffectParam("Location", "(xsVectorGetX(tpSocketPosition) + 5 ) + ",0," + xsVectorGetZ(tpSocketPosition)", true);

I tried using the simple syntax, without quotes, and the compiler doesn't accept something like this:

      rmSetTriggerConditionParam("Socket", tpSocketSelect);

The "PlayerID" parameter name is from the official typetest.xml trigger definition document which ships with the game. I tried using both versions, Player and PlayerID, without noticing any difference.

I've also tried using a loop to have this trigger run for all players, but again to no effect. So this is just a version to test how it would work for at least Player 1. If I get it to work for one player, I'll get it to work for all.

Do you think it's a problem with the syntax difference (this is a AoE3 vanilla map, while the code from Ensemble is from a TAD map) or I made some mistake in the code?
« Last Edit: June 04, 2013, 04:39:15 AM by Neuron » Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #5 on: June 04, 2013, 09:58:48 AM »

Nevermind. I managed to make the trigger work.

Some triggers can be a bit tricky about syntax, especially those which require user input in the editor (such as unit selection).
Logged
Pages: [1] Print 
pftq Forums  |  Game Design  |  Age of Empires III  |  RMS function or something else? « previous next »
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.21 | SMF © 2006-2007, Simple Machines | RSS Feed Valid XHTML 1.0! Valid CSS!
Page created in 0.069 seconds with 21 queries.