As i've promised: Vehicle Reinforce & working with Groups !
Many people have been wondering how this actually works (including myself :lol:) and i'm happy to inform you that i've worked out the details. More below !
First thing you need to do, after creating a new building- mining- or fightGroup, is assign units to it using TakeUnit. A buildingGroup should have a structure factory and one or more convecs, a miningGroup should have cargo trucks, and a fightGroup is of course suppose to have tanks. Nothing changes so far.
You can now set the target count for your groups, using SetTargCount. This code will instruct the AI to keep your fightGroup at 3 RPG-lynx at all time; BUT, nothing happens until you've used the VehReinforceGroup function (this has advantages):
FightGroup1.SetTargCount(mapLynx,mapRPG,3)
After you've constructed a vehicle factory, create a NEW buildingGroup (this is very important - i'll explain why this is later) and assign the VF to it, again using TakeUnit:
Unit VF;
TethysGame::CreateUnit(VF,mapVehicleFactory,LOCATION(40,10),0,mapNone,0);
VFgroup1=CreateBuildingGroup(Player[0]);
VFgroup1.TakeUnit(VF);
VFgroup1.RecordVehReinforceGroup(FightGroup1,0);
This code will instruct the new group (VFgroup1) to reinforce any lost units from FightGroup1. Whenever a unit is missing in the destinationgroup, the VF will create the unit, and add it automaticly to the fightGroup.
Using UnRecordVehGroup you can remove FightGroup1 from the reinforce LIST - yes LIST. Everytime you use RecordVehReinforceGroup you will add a new group to be reinforced by this VF. This is where the second param comes in: it is the construction priority.
Now using this priority might seem a bit odd at first, but you can add several fight/mining/buildingGroups to the VF reinforcelist, and subsequently set different priorities to them. For example:
VFgroup1.RecordVehReinforceGroup(buildingGrp1,100);
VFgroup1.RecordVehReinforceGroup(miningGrp1,200);
VFgroup1.RecordVehReinforceGroup(fightGroup1,4000);
This code will construct all the tanks, trucks, convecs and whatever else you've set with SetTargCount, and it will do so randomly BUT, it'll randomise with those priorities. 0 is the lowest priority, and MAX_INT would be the highest. In the example above chances are units for fightGroup1 will be produced first, then miningGrp1, and finally buildingGrp1. Units are created one at a time, and the priorities are randomised every time. It's all a matter of chance: if i create a miningGrp1 with 10 trucks, and the fightGroup1 with 10 lynx, chances are pretty good, before the 10th lynx is produced i'll have a couple of trucks ready. I hope you understand this - the order set out with the priority param, is not absolute but more like a guideline for the AI.
Now, the other side of the reinforce story is the targCounts: you can create more than 1 targCount for a group! So, if you have a fightGroup and you want it to have 3 RPG-lynx, 6 EMP-panthers and 2 ESG-tigers, you can program like this:
FightGroup1.SetTargCount(mapLynx,mapRPG,3);
FightGroup1.SetTargCount(mapPanther,mapEMP,6);
FightGroup1.SetTargCount(mapTiger,mapESG,2);
There is no way to set priorities within a group. Group members will be created completely by random.
A final note (trust me on this one): for basic AI programming, you can suffice with adding the VF into the buildingGroup, and set the ReinforceGroup to point to itself. But when you are going to "mess around" with groups, you will get into trouble later, as vehicle counts will get lost, and the AI will not reinforce those groups correctly anymore, in which it is part itself, THEREFOR i suggest you create a new buildingGroup wich contains only the VF. This, of course, is not necessary if your VF-group does not have any TargCount set, or when 2 VF's keep each other up.
Next time:... uhm.. ? what do you guys wanna know ? more on triggers ? Let me know!
Okay, since i got no requests :P .. working with Groups
First of all: i'm not saying this is THE way to do it, but it is MY way. if you like it, follow my lead - if you don't: submit a better way! If you don't have a better (or easier) way, don't complain...
okay; here we go:
Ever had your AI attack enemy units, but all their tanks are spreading around, not really co-ordinating their attack ? ..can't keep track of all your units ? well .. i had these probs, but i managed to solve them... somehow.
When my DLL gets loaded, i call a procedure SetupGroups from the InitProc rigth at the start. In this procedure i create all fight/mining/buildingGroups i need for the whole mission. I found this to be the safest way, since accessing a non-initialized group causes op2 to crash real bad { exception - read of address... }
So, you'd see something like this in there (actual code from a working mission):
waitGrp=CreateFightGroup(Player[1]);
waitGrp.SetRect(MAP_RECT(40,4,60,12));
waitGrp.SetLights(1);
moveGrp=CreateFightGroup(Player[1]);
moveGrp.SetRect(MAP_RECT(80,24,90,32));
moveGrp.SetLights(0);
attackGrp=CreateFightGroup(Player[1]);
attackGrp.SetRect(MAP_RECT(40,4,60,12));
attackGrp.DoAttackEnemy();
attackGrp.SetLights(1);
Now, obviously these are 3 fightGroups, but you should initialise any and all group types here. I've had it happen too many times i try to find out what a certain group is doing (specificly fightgroups), in a timetrigger, when they're not even created yet. So, this way, i won't get any of those problems anymore!
The trick about creating a "smart" AI, is to have'm create the units one-by-one at the VF (as i've demostrated in my earlier "Lesson"); then move its tanks to a safespot when your group is of sufficient size. To make it even smarter: my AI's will start creating a NEW attack group, the moment the 'old' group moves off to its safespot, in fact the VF is continuously producing units and creating new attack groups in this way.
When your group is the correct size (say 3 units), you can move them over to a new group using TakeAllUnits. For this you will need 2 groups to make it work:moveGrp.TakeAllUnits(waitGrp);
This will automaticly move all units from waitGrp to moveGrp. If you'd use TakeUnit to move them one by one, it would work also, BUT the units would be part of both groups, so you'd have to use RemoveUnit to remove it from the waitGrp! When using TakeAllUnits, the removal actions is included in the fuction call.
AFTER the call, moveGrp will have the 3 tanks, and waitGrp will be empty again. This by itself is a trigger for the VehReinfroce -if used correctly- to produce new units into waitGrp:
waitGrp.SetTargCount(mapLynx,mapRPG,3);
VFgroup.RecordVehReinforceGroup(waitGrp,0);
The above code could already be set up in the SetupGroups procedure (it should -coz it's group settings).
The new group of 3 (moveGrp) will be going to its Rect as coded into the moveGrp initialization. This means, you do not have to move the units by yourself with DoMove! You might also have noticed i've set the lights of moveGrp to zero, wich means they're OFF .. this is a sneak attack.
Once the 3 units get to their destination rect, you can let them wait for some time, and then move those 3 units to the third fightGroup:
attackGrp.TakeAllUnits(moveGrp);
As you might have guessed, this'll remove all units from moveGrp, and put them into attackGrp. So far i have not yet issued 1 single command to my tanks! All i've done is move the units from one group to another. All the commands are precoded into my SetupGroups: being part of the group will automaticly issue the commands to the units. So, your 3 tanks should now be on their way (with lights on!) to the nearest enemy unit and attack it. Using DoAttackEnemy, as i have, tells them to attack any military unit. Next to tanks, this includes tokamaks, factories, GPs etc. Agridomes & residences for example are left alone.
When no more enemies can be located, the group will move to its SetRect, in this case back to our base.
---------------------------------------------------------------------------------------------------------------------------
So far our lesson for today;
next time i will explain HOW to figure out when your groups are up to strength and at the right spot.
Hey there !!
..after 'some' silence i decided that i'd have to add something again. Well, that and haxtor asking too many questions .. lol :P juz kidding!
So, the song-list: HOW the heck do i play those in-game songs ?
The songs are obviously set up using TethysGame::SetMusicPlayList
The procedure accepts 3 params: 2 indexes (or was indices?) and a list of SongIds. Now to start with the latter: the SongIds are nothing more then an array of song IDs .. simple isn't it?
I've added the following lines to my Outpost2DLL.h, but until such time that a uniform NEW version of this header becomes available - it is subject to change, so don't complain if your own source files won't compile anymore after you've downloaded any new version of the mentioned header. In other words: no guarantees and no liability crap about this posting, okay?
Here we go (songlist has been edited; this is the way you will find it in the new SDK in enums.h): enum SongIds {
songEden11= 0x00,
songEden21,
songEden22,
songEden31,
songEden32,
songEden33,
songEP41,
songEP42,
songEP43,
songEP51,
songEP52,
songEP61,
songEP62,
songEP63,
songPlymth11,
songPlymth12,
songPlymth21,
songPlymth22,
songPlymth31,
songPlymth32,
songPlymth33,
songStatic01,
songStatic02,
songStatic03,
songStatic04,
songStatic05
};
The way you use those enums is by creating an array of type enum SongIds (as expected):SongIds songs[]={ songPlymth11, songPlymth12, songEP51, songEP52 };
This will set up the game to play those 4 songs (Plymth11.raw; Plymth12.raw; EP51.raw and EP52.raw).
The other 2 params are important:
Param1 sets the array size: in our case it should be 4
Param2 tells op2 where to re-start playing after it's done. To explain this i've put 2 songtypes into the SongIds list. When i set param2 to 0 it'll play all 4 songs, then return to song ID#0 (=Plymth11) and start over again etc... When i set param2 to 2 it'll first play all 4 songs, then return to song ID#2 (=EP51) and continue from there..indefinately! This gives the player those 2 easy/soft songs, and then go on to the uptempo EP-songs, and never go back again (unless you restart the mission)
This should play the songs i've explained above:
TethysGame::SetMusicPlayList(4,2,songs);
...next time .... ? who knows - drop me a message what you'd like to learn.
If i don't know how it works myself: i'll be honest about that, and/or i'll try to figure it out, and let you know after a while!
Entil-zha !
Hi again, all you (new) coders !!
Since i keep seeing this question over and over, i thought it might be a good idea to shed some light on this topic: TRIGGERS
To learn about HOW they work, i'll explain a little bit of the game-engine, so you'd understand the trigger-system.
Outpost 2 works with whats called "Ticks" and "Time"; Time is what you see in the game, when you click the communications tab. Ticks are only used internally by the game-engine. Thedre are 100 ticks in 1 time-mark. Every move or other graphic change you see on the screen is done 1 tick at a time. For example: turning a truck 180 degrees takes about a dozen ticks. The faster you set your game-speed, the faster the ticks will go by. The game-engine has to perform a LOT of things in 1 game-tick; not just anything that's been programmed in the mission DLL but also ALL graphics changes you see on the screen, including drawing up a new image if you move the view-box. This is the reason why slower pc's won't show much change in gamespeed if you move from let's say speed 6 to speed 10. This is because at speed 6 your pc is running at its peak already, and simply cannot cope with more game-ticks in a second.
Now, the easiest trigger to create is the TimeTrigger: it triggers at a certain time.CreateTimeTrigger(int boolEnable, int boolNoRepeat, int time, char const *triggerFunction);
I'll go through all the params:[ol type=\'I\'][li]int boolEnable: Make sure this param is 1 to enable your trigger. Setting it to 0 will disable your trigger, and it doesn't make sense to create a trigger that won't work;[/li][li]int boolNoRepeat: this one is tricky: set it to 1 to run only 1-time (=no repeat). Set it to 0 to run indefinately. More on this later;[/li][li]int time: The tick when this trigger should fire, starting from NOW. When set in InitProc, the trigger will fire when the tick is reached. If set at any other time in the game, it fires at 'time' gameticks after the moment it was created;[/li][li]char const *triggerFunction: This is the function that is called when the trigger is fired, in this case: the 'time' is reached.[/li][/ol]The items marked in blue above appear in ALL the trigger functions, and are key-params. The 'time' param only applies to a TimeTrigger.
Example:
CreateTimeTrigger(1,1,200,"Attack");
This code will create a trigger that will call the function Attack() one time only, after 200 gameticks. IMPORTANT: 200 game-ticks is only 2 timemarks. Everytime you use this, you have to multiply the time x100 !!
The function Attack should be defined like this:
[DoHtml]
<table><th>Trigger function
</th><th>It fires when...
</th>
<tr><td>CreateBuildingCountTrigger </td><td>a certain number of buildings is reached</td></tr>
<tr><td>CreateVehicleCountTrigger</td><td>a certain number of vehicles is reached</td></tr>
<tr><td>CreateCountTrigger</td><td>a certain number of units is reached</td></tr>
<tr><td>CreateAttackedTrigger</td><td>a unit is attacked (not functioning)</td></tr>
<tr><td>CreateDamagedTrigger</td><td>a certain part of a group is killed</td></tr>
<tr><td>CreateEscapeTrigger</td><td>a unit escapes to a given region</td></tr>
<tr><td>CreateKitTrigger</td><td>a certain kit is ready</td></tr>
<tr><td>CreateMidasTrigger</td><td>midas condition is reached</td></tr>
<tr><td>CreateOnePlayerLeftTrigger</td><td>uhm.. make a guess</td></tr>
<tr><td>CreateOperationalTrigger</td><td>a certain number of buildings is operational</td></tr>
<tr><td>CreatePointTrigger</td><td>any unit moves over a point</td></tr>
<tr><td>CreateRectTrigger</td><td>any unit moves into a region</td></tr>
<tr><td>CreateResearchTrigger</td><td>some research is completed</td></tr>
<tr><td>CreateResourceTrigger</td><td>a certain amount of resources is reached</td></tr>
<tr><td></td></tr>
<tr><td>CreateEvacTrigger</td><td>unknown/untested at the moment</td></tr>
</table>[/DoHtml]
int boolNoRepeat: Be careful using this. In a timeTrigger it creates a trigger everytime the set timeticks have passed. If i changed the boolNoRepeat from 1 to 0 in the example way above, the trigger would fire every 2 gamemarks. BUT; if i use it with other triggers, it might fire every single gametick when a condition is met! eg. if you'd use this in the OperationalTrigger, it would first fire when the building in that trigger becomes operational, and would then fire every gametick from that point on, making your DLL run the triggerFunction code 100 times per time-mark!
Good luck with those triggers. Oh, don't ask about the set trigger. I DO know what it does and how it should be used, but if you're not 100% comfortable yet with all the triggers, you certainly won't be able to use the SetTrigger...
[EDIT] CreateAttackedTrigger doesn't seem to work; using it will crash op2. Also, not a single original mission uses this trigger, so its code is either never written, or it just wasn't tested. Either way: DONT USE IT!
[EDIT2] I've been testing and debugging CreateSpecialTarget (and GetSpecialTargetData and Unit::ClearSpecialTarget), and it seems OP2 has its shortcomings. It is kinda buggy, and i won't explain it here. So please don't use it as well
Entil-Zha!
I haven't looked recently, but I did have the function which did research in my AI test to only assign up to the maximum number allowed for that tech. (I'm not sure if that's the version I released). I didn't have a function to return the number of available scientists however. So it had the problem of making all your buildings go offline once too many scientists died since it kept assigning too many of them to research.
Anyways, I'd imagine a simple memory scan could reveal the location of a variable that displays the current number of available scients. This is viewable from the morale display, so it's calculated or stored somewhere. If it's in a global variable, then the problem is easily solved. If it's calculated, well..., then I guess you'd need to do that yourself from available data. Although, all the values you should need are most likely stored in global variables (for efficiency reasons) so once you get the right addresses, the rest of this problem becomes easy.
Edit: I looked into this. The number of colonists you have (for Workers, Scientists, and Kids) all seem to be modified in a function at address 0x00471D60. It accesses variables in the internal player class. There is an array of these classes with base address 0x0056EF1C. The offset of the number of Workers, Scientists, and Kids, is respectively 0x94, 0x98, 0x9C. Shortly after this you can also find things like the number of available Scientists (0xA8), number of available/assigned workers? (0xCC), amount of power total (0xAC) and amount of power available (0xB8). The player class has a size of 3108 bytes. So you can access the data using the address: 0x56EF1C + playerNum*3108 + offsetOfData.
For playerNum = 0, the number of available scientists is at: 0x56EF1C + 0*3108 + 0xA8 = 0x56EFC4.
A really simple way to get at this value would be something like the following:
int *numAvailableScientists = (int*)0x56EFC4;
int num = *numAvailableScientsts; // Read the value and store it in "num"
*numAvailableScientists = 5000; // Give lots of available scientists =)
Note: I haven't compiled or tested the above code, just typed it right there as the sort of way you might go about doing this. I'll leave it to whoever wants to try it to tell me if it works. =)
Btw, if might shed some light on a few issues if someone takes a closer look at that function where the stuff is modified. With a bit of assembly knowledge, you can find out if having more than one nursery/university actually helps, med center effects, number of workers vs. number of scientists, birth rates, death rates, and all that good stuff. (I've looked at it a little bit, so I have a little idea about a few of these things).
[size=8]An addition is in place, after working with groups some more recently:[/size]
Any FightGroup can have upto 8 Guardedrects. If you try to add a 9th, outpost 2 will crash. Calling ClearGuarderdRects will remove all entries, and again you can add 8 GuardedRects.
Because of the way that FightGroup works, when using FightGroup::SetRect, it internally calls AddGuardedRect to add the region to its list of guarded rects. This way the rect where the units are stationed is also guarded. At the same time, this reduces the number of guarded rects that you can add to 7.
Also, when changing the SetRect of a FightGroup { since it calls AddGuardedRect } the old rect that was assigned to the group is STILL in the guarded rects list!
So, please: when you need to change the groups location, use the following command sequence:
Group1.ClearGuarderdRects();
Group1.SetRect(NewMapRect);
If you added extra guarded rects, you have to add them again, because they are gone also now.