Welcome! Please Login or Register!
October 15, 2024, 09:13:36 PM
Home Login Register
pftq Forums  |  Game Design  |  Age of Empires III  |  Can you select collections of objects in a map script or just individual objects 0 Members and 1 Guest are viewing this topic. « previous next »
Latest News!Lunar Trigger(Aug 27, 2023)
Pages: [1] 2 3 Print
Author Topic: Can you select collections of objects in a map script or just individual objects  (Read 14509 times)
Neuron
Member
*****
Offline Offline

Posts: 30

« on: June 16, 2013, 05:49:53 AM »

Hi, pftq. Sorry to bother you again with RMS qeustions, but there's no one else I can ask (there are no more people who know RMS active anywhere).

What I'm trying to do:

1. Create a herd of bisons in a spot, in the middle of an area. -- Easy and done

2. Place an object (like a flag) in the middle of the area which I can later use as a reference point object -- Easy and done

3. Create a trigger which checks if the the bison herd has left the area. If the herd left the area, create an effect which makes the herd move back to the area.

Now, here's the problem for point 3:

I managed to do this without any problems in the scenario editor, the whole thing works in the editor perfectly. I achieved this by:

- creating a trigger
- adding a condition ("Distance to Point") for the herd: when the herd which is selected as a Source Unit moves away from Distance point which is set at the flag in the middle of the area, then
- effect ("Move To Point") happens: the selected units (the herd) move back to the flag, which is defined as the Distance point.

This works almost flawlessly in the scenario editor, because of course it's very easy to select units by hand and set a point they should move to. However, in a map script this has to be done using functions which locate the Source Unit without any user input. So that means using functions such as:

rmGetNumberUnitsPlaced  
rmGetUnitPlaced  
rmGetUnitPlacedOfPlayer  

Now, the problem is as much as I tried using these functions, they don't select more than one unit. So these functions fail to select collections of units like animal herds. I'm not sure if this has anything to do with bisons not being able to belong to a player, since even if you define them in the map script as belonging to Player 1, they will spawn as belonging to nature. But still, even in this case, at least one of them still obeys the triggers I wrote and moves.

So this is obviously some limitation these functions have, since if it works for one object the triggers work, but the function fails to select more than one animal in the herd.

Do you have any idea if there's any workaround this problem to get the whole herd selected and make it move using triggers? Thanks.
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #1 on: June 16, 2013, 10:17:45 AM »

I would normally use kbQueries for this but it gets complicated if you're not familiar (basically you can use kb to select all units within a range and then run triggers only on them).

Without kb, I would try to create the units using Army Deploy.  That way you always have a reference to the units.  Then you can use stuff like Army Distance to Point.
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #2 on: June 16, 2013, 12:19:00 PM »

I haven't used kb functions yet, but I suppose you mean to use them with that code injection method, in a Send Chat trigger. I'd like to try this method too if I manage to understand what the code injection syntax mean, for example why one part of the code is outside of quotes and the other is not. I know it's supposed to carry the code over to another layer of the game, but I'm not sure why it works this way. I mean what is the generic syntax of this method, because from I've seen it's hard to separate what belongs to the specific script and which is the general syntax.

I will try and see if using Army Deploy gets a better result, although it seems this might be a limitation typical to the unit type, that is stuff which belongs to nature/gaia can't be owned by players so it can't be selected apparently like you select units. So, if you give a player a herd of bisons, the game compiler will force the herd to belong to gaia no matter what, so functions which are used to select unit groups no longer work. I will try first and see if the Army Deploy method works though. It might be possible, taking into account that nature/gaia is basically hardcoded as Player0.

Can you give an example of how this could work using kb functions? You can paste from a script you know where this was used in a similar way on nature units, if you don't have the time to write it. Thanks.
« Last Edit: June 16, 2013, 12:23:00 PM by Neuron » Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #3 on: June 16, 2013, 05:38:03 PM »

Nope, it doesn't work with Army Deploy.

This is the first method I used, as I explained above:

// Create bison herd

int bisonID=rmCreateObjectDef("bison herd per player");
rmAddObjectDefItem(bisonID, "bison", rmRandInt(12,16), 12.0);
rmSetObjectDefCreateHerd(bisonID, true);
rmSetObjectDefMinDistance(bisonID, 0.0);
rmSetObjectDefMaxDistance(bisonID, 15.0);
rmPlaceObjectDefAtLoc(bisonID, 0, 0.25, 0.5, 1);

// Select the herd and assign a vector variable to its position

int herdSelect = rmGetUnitPlaced(bisonID, 0);
vector herdPosition = rmGetUnitPosition(rmGetUnitPlacedOfPlayer(bisonID, 0));

// Place a flag in the middle of the area in which the herd will spawn

int playerFlag=rmCreateObjectDef("Player area flag");  
rmAddObjectDefItem(playerFlag, "SPCFlag", 1, 0);  
rmPlaceObjectDefAtLoc(playerFlag, 0, 0.25, 0.5, 1);  

// Select it and assign it vector position coordinates

int flagSelect = rmGetUnitPlaced(playerFlag, 0);
vector flagPosition = rmGetUnitPosition(rmGetUnitPlacedOfPlayer(playerFlag, 0));

// Trigger area contain action

 rmCreateTrigger("AreaContainCheck");
 rmSwitchToTrigger(rmTriggerID("AreaContainCheck"));
 rmSetTriggerActive(true);
 rmSetTriggerLoop(true);
 rmAddTriggerCondition("Distance to Point");
 rmSetTriggerEffectParamInt("SrcObject", herdSelect);
 rmSetTriggerEffectParam("DstPoint", xsVectorGetX(flagPosition) + ",0," + xsVectorGetZ(flagPosition));
 rmSetTriggerConditionParam("Op", ">=");
 rmSetTriggerConditionParam("Dist", "30");
 rmAddTriggerEffect("Move To Point");
 rmSetTriggerEffectParamInt("SrcObject", herdSelect);
 rmSetTriggerEffectParam("DstPoint", xsVectorGetX(flagPosition) + ",0," + xsVectorGetZ(flagPosition));

This didn't work. If I replace the vector functions I used to determine the flag position area with actual vector coordinates such as (220,0,330) -- a random example -- one bison starts obeying the trigger, so it starts moving from the first vector point to the second. The rest of the herd ignores the rule. So it looks like the trigger doesn't have any error, but the rmGetUnitPlaced functions seem unable to select groups of objects which belong to nature.

Next, I tried the method you mentioned with the Army Deploy trigger.

// Create a location in which the army will spawn

   vector vctHotSpot = xsVectorSet(220.000000,4.000000,330.000000);

// Define army
   int armyID = rmCreateArmy(1,"Army1");                  
   bool grow = rmAddUnitsToArmy(armyID, 291, 20);    // ProtoID Bison

   if(grow)
   {

//Trigger

       rmCreateTrigger("Army Size");
       rmSwitchToTrigger(rmTriggerID("Army Size"));
       rmAddTriggerCondition("Always");
       rmAddTriggerEffect("Army Deploy");
       rmSetTriggerEffectParamArmy("SrcArmy",1,10);
       rmSetTriggerEffectParam("ProtoName","Bison");
       rmSetTriggerEffectParam("Location",xsVectorGetX(vctHotSpot) + ",0," + xsVectorGetZ(vctHotSpot));
       rmSetTriggerEffectParamInt("Count",20);
       rmSetTriggerEffectParamInt("Heading",90);
       rmAddTriggerEffect("Army Move");
       rmSetTriggerEffectParamArmy("SrcArmy",1,10);
       rmSetTriggerEffectParam("DstPoint","110,0,200");
       rmSetTriggerEffectParamInt("EventID",-1);
       rmSetTriggerEffectParam("AttackMove","true");
       rmSetTriggerEffectParam("Run","true");
       rmSetTriggerEffectParamFloat("RunSpeed",5.0);
}

This didn't do anything. The bisons just sit in a pack and don't move.

I'm guessing that this is because functions which can be used in a RM script can't select gaia objects and control them. Some objects like deers or bisons (or other stuff which can't move around, like mines, trees or sockets) can't be owned by a player so the game compiler probably ignores the commands and forces them to belong to gaia.

What I was looking for was a method to control herds behaviour by making them stay in an area using a looped trigger.

It seems the usual RM functions can't do that. Did kb functions ever managed to control gaia objects in groups?
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #4 on: June 16, 2013, 08:53:37 PM »

Did you try creating the army under Player0? Sorry it's been a while so the syntax is not as familiar to me.
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #5 on: June 17, 2013, 07:53:22 AM »

If I assign Player0 as the owner of the army, it works, yeah. It goes from one point to another, as commanded. Grin

But there's a trade-off -- since the bison bunch is defined as an army it stops behaving like a herd. So if a villager shoots a bison to herd the group, the bison bunch no longer moves together -- each bison plays its animation individually, but there's no more collective behaviour unless you command it as an army.

A modder told me he managed to make a vulture belong to a player other than 0/nature and be controlled by a human user. It seems once you make it controllable by a user which can give commands, the object stops behaving like nature. They can still play their animations, but they stop obeying the scripts which program nature tactics.

Thanks for the help.

If you have time, maybe you can give an example of how a kb function could be used in a map script. Does it have to be called from a Send Chat trigger to work?
« Last Edit: June 17, 2013, 08:10:22 AM by Neuron » Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #6 on: June 17, 2013, 09:05:45 PM »

Glad it worked out.  Yeah, that's the gist of it.  You're mis-using the Send Chat to execute more code.

This is from Fort Wars for making units in an area each move to themselves (stop)... it's a little more complicated than it has to be (because I intertwined it with a lot of other code).  tEffect is just shorthand for rmAdd/SetTrigger...

void custEffect(string xs="") {
   tEffect("SetIdleProcessing");
   tEffectPar("IdleProc", "true); "+xs+" trSetUnitIdleProcessing(true");
}

void addXS(string code="") {
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "*/"+code+"/*", false);
}

void unitstop(int p=0) {
   custEffect("unitstopper("+p+", "+rmXFractionToMeters(getFl(SpawnIslandX, p))+", "+rmZFractionToMeters(getFl(SpawnIslandZ, p))+", 15);");
}

// kb query
addXS("void unitstopper(int p=0, float unito1=0, float unito2=0, int radius=0) {");
addXS("int lastpl = xsGetContextPlayer(); xsSetContextPlayer(p); kbLookAtAllUnitsOnMap();");
addXS("int revquery=kbUnitQueryCreate(\"unitstopper\"+p);");
addXS("kbUnitQuerySetPlayerID(revquery, p);");
addXS("kbUnitQuerySetUnitType(revquery, "+objID_Unit+");");
addXS("kbUnitQuerySetPosition(revquery, xsVectorSet(unito1,0,unito2));");
addXS("kbUnitQuerySetMaximumDistance(revquery, radius);");
addXS("kbUnitQuerySetState(revquery, 2);");
addXS("kbUnitQueryResetResults(revquery);");
addXS("int revresults=kbUnitQueryExecute(revquery);");
addXS("for(i=0;<revresults) {");
addXS("int revunit=kbUnitQueryGetResult(revquery, i);");
addXS("trUnitSelectClear(); trUnitSelectByID(revunit);");
addXS("int x=xsVectorGetX(kbUnitGetPosition(revunit));");
addXS("int y=xsVectorGetY(kbUnitGetPosition(revunit));");
addXS("int z=xsVectorGetZ(kbUnitGetPosition(revunit));");
addXS("trUnitMoveToPoint(x,y,z);");
addXS("}xsSetContextPlayer(lastpl);}");


You  notice that with XS code, I basically take apart the code in the trigger.xml file and use it directly to build my own stuff.
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #7 on: June 24, 2013, 10:00:59 AM »

Hm, you are also using a Send Chat trigger in a function (addXS) to create another function (unitstopper) whose statements are later added dynamically with the addXS function. Wouldn't it be easier to simply create the function with the kb commands statements and then use it? Or did you try this and it didn't work to have it defined before the main function?

Also, did you divide the code in pieces so that you can later script with less code, or just to scramble the code and prevent editing?

Well, I'll try using first some simple kb functions with a Send Chat until I get a handle on this method.
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #8 on: June 24, 2013, 10:09:26 AM »

I have many kb functions, that's why it's divided up.  You can do it all in one function.  I ended up re-using the addXS a lot for other stuff too.
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #9 on: June 24, 2013, 07:11:50 PM »

So what exactly does the compiler have to see in order to accept the code?

It seems to me the syntax differs from case to case. For example when you call a tr function with the sendchat function you simply put it in quotes:

addXS("trUnitSelectClear(); trUnitSelectByID(revunit);");

So this is the syntax for tr functions without arguments, just inserted as string.

But in other maps (like AoE Racer), when you use the sendchat function to call tr functions which have arguments, you use a different syntax:

_LFDX("trQuestVarSet(\""+_LFPB+"\", trQuestVarGet(\""+_LFPB+"\") "+_NPLJ+" trQuestVarGet(\""+_PYAM+"\"));");

So, again, the tr function is simply inserted in quotes, but its arguments are preceded by a backslash and quotes. Then the actual variable is added then "\", etc.
I guess that's what I don't understand about this syntax -- is why does it vary from no backslash for functions without statements to first backslash two quotes and then quote-backslash-quote for tr functions with statements/variables/conditions. Is there any logic for the syntax to be this way, or it just is?

I tried to test this method with a sendchat and a tr function without statements/parameters.

So, I created a function with a Send Chat trigger:

int num=0;
void xsInject(string code=""){
   rmCreateTrigger("Code feed"+num);
   rmSwitchToTrigger(rmTriggerID("Code feed"+num));
   rmSetTriggerActive(true);
   rmSetTriggerRunImmediately(true);
   rmSetTriggerLoop(false);
   rmAddTriggerCondition("Timer");
   rmSetTriggerConditionParamInt("Param1", 10);
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "*/"+code+"/*", false);
   num=num+1; }

And then I inserted an easy-to-test tr function:

xsInject("trRevealEntireMap();");

No result.  Undecided
Well, I actually get an in-game string message with the command.

I also tried using the other syntax with \"" and "\" but it fails to load.

 Huh

LE. And then, I found a reference to this method in an aom/aoe scripting guide, but the syntax is again different:

void rmInsertConditionCode(string xs="") {
        rmAddTriggerCondition("Timer");
        rmSetTriggerConditionParam("Param1","0==("+xs+"));//");
}

void rmInsertEffectCode(string xs="") {
        rmAddTriggerEffect("SetIdleProcessing");
        rmSetTriggerEffectParam("IdleProc", "true); "+xs+" //");
}

Are there more methods than one to make this work?
« Last Edit: June 24, 2013, 07:35:12 PM by Neuron » Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #10 on: June 25, 2013, 07:25:22 PM »

It would have been easy to just copy code and replace the strings/variables, but I'm interested more in understanding the method, I'm not interested in copying stuff I don't know how it works. And I have my own ideas for maps, I don't want to copy code from other maps.

I guess I have no choice but to unscramble AoE Racer and see how the code works. Thanks for the help, so far.
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #11 on: June 25, 2013, 10:20:16 PM »

The syntax doesn't differ.  When you use a variable without breaking the string, it means you are referencing a variable created via the XS layer of the code. (so revunit is a variable defined like addXS("int revunit = 0;")Wink
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #12 on: June 26, 2013, 05:22:15 AM »

So why doesn't it work with functions which have no variables? There's no need to "break the string" in a function with no variables, so it should work simply by placing the tr/kb/xs function inside the quotes and execute it.

This is what you wrote in your script:

void addXS(string code="") {
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "*/"+code+"/*", false);
}

addXS("trUnitSelectClear(); trUnitSelectByID(revunit);");

(I suppose you had other functions defined before for creating and/or switching to trigger.)


This is what I wrote:

int moar=0;
void xsInject(string code=""){
   rmCreateTrigger("Code feed"+moar);
   rmSwitchToTrigger(rmTriggerID("Code feed"+moar));
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "*/"+code+"/*", false);
   moar=moar+1; }

xsInject("trRevealEntireMap();");

The result of this is that the function within the quotes is output in game as a string message:

*/trRevealEntireMap();/*

Although I used the same syntax as the one you used in that script.
Logged
pftq
Administrator
*****
Offline Offline



Posts: 4202

WWW
« Reply #13 on: June 26, 2013, 03:34:05 PM »

Forgot the header/footer.  You need to enclose each block of XS code with the following.

void beginGlobals() {
   rmSwitchToTrigger(rmCreateTrigger("globals_start_"+guid()));
   rmSetTriggerActive(false);
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "\"); }} /*", false);
}

void endGlobals() {
   rmAddTriggerEffect("Send Chat");
   rmSetTriggerEffectParam("Message", "*/ rule _globals_end_"+guid()+" minInterval 4 inactive { if (1==0) { trChatSend(0, \"", false);
}

So:
BeginGlobals();
addXS(...);
EndGlobals();
Logged
Neuron
Member
*****
Offline Offline

Posts: 30

« Reply #14 on: June 26, 2013, 05:00:27 PM »

OK, I'll try this method too.

I suppose you have a function guid() defined earlier on, which provides an increment for trigger creation and for the ending of each XS code insertion.

And secondly, I also suppose that this syntax has something hardcoded in it.

Like the header always starts with \"); }} /*
-- and the footer always ends with the creation of a rule. (I'm trying to understand why this method has this effect on how the game environment evolves.)

Does that rule have to be called _globals_end_ or can I call it any other way? Just curious if this is something hardcoded in the game.

Thanks for the help, again.

--------------------

Hm, so this is what the interpreter is going to see:

\"); }} /**/"+XS code+"/**/ rule _globals_end_"+guid()+" minInterval 4 inactive { if (1==0) { trChatSend(0, \"

This was the "syntax" I was looking for.
« Last Edit: June 26, 2013, 05:16:53 PM by Neuron » Logged
Pages: [1] 2 3 Print 
pftq Forums  |  Game Design  |  Age of Empires III  |  Can you select collections of objects in a map script or just individual objects « 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.125 seconds with 21 queries.