AI Coding

From OPU Wiki

Table of contents

Building Construction

I think most of you figured this out by yourselves, but here it is anyway:

  • create a new buildingGroup
  • put at least 1 convec and 1 SF in it using TakeUnit
  • record as many buildings as you like (i haven't found the limit yet) using RecordBuilding. The (x,y) location of any building is determined by the point where the 2 tubes that come out of it meet ("underneath" the building, where you cannot actually see it). Be advised: the (31,-1) offset is to be used
  • set a MAP_RECT where the vehicle(s) of your buildingGroup will moveto when they have nothing to do. It would be a good idea to have them close to your SF - but don't make the rect too small to manouver
  • Earthworkers can be added to the buildingGroup (using TakeUnit again) if you want to also construct tubes and/or walls
  • to have your earthworkers reconnect buildings that are no longer connected, due to a destroyed building somewhere in the line, record tubes THROUGH buildings; that way your earthworker will always build those missing tubes. If there's a building in that spot already, it won't cause any problems.
  • be advised that the order in wich things are being build is determined by the order you've recorded them. This also means, that if a couple of buildings gets destroyed they will be rebuild in the original order set out with RecordBuilding. The same goes for tubes/walls
  • finally, you can ad a Robo Miner to your group, and record a mine - the miner will build the mine for you


Now, some remarks about building construction:

  • A remark about the construction order: be sure to record a mine & smelter early - even if those buildings are present when the mission starts, because you've used CreateUnit to put them onto the map: ANY building in the recorded list that already exists, will be ignored (until it gets killed). Imagine: you record a VF before you record any smelters. Now if both get destroyed, your ai might use its last ore to build the VF, and have nothing left to construct the smelter kit. A simular problem exists when you've recorded buildings that require rare ore, even though you might have 50,000 common ore available, a simple guardpost recorded right after that will not be built until the ai constructs the previous building. Keep that in mind !
  • the SF will automaticly build the kits as the convec(s) need them. If one convec is moving towards its construction location, another kit will not be started. As soon as that convec starts its actual construction, only then will the SF start building the kit, and the next convec will move towards the SF (if there's still convecs in your group waiting for instructions)
  • you can speed up your ai, by putting ready-made kits in the SF, using SetFactoryCargo - careful, if used with anything else but an SF (or SpacePort), op2 will crash!

Vehicle Reinforce

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: CODE

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.


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.

Group Members

What do they do and how do i use them ?

In short: there are 3 types of groups:

  • FightGroups
  • MiningGroups
  • BuildingGroups

Do NOT use ScGroup by itself, and do NOT create groups using the new keyword; use the CreateGroup functions instead!

The type of group you use depends on what you want the group to do.

  • If you want to build something (eg. buildings, tubes, walls) use the BuildingGroup type. Use this type also if you want to create new vehicles at a VF. Theoreticly you can assign ANY unit type to this group, but it's advisable to only assign units with building capabilities, such as ConVecs, Earthworkers, RoboMiners and factories;
  • Obviously, a MiningGroup is used for mining operations. You should only assign cargotrucks to this type of group;
  • FightGroups are used for grouping units for grouped actions. This does not necessarily have to be fighting actions, it can be any action.

If you do not have any special action for the group (you only need the units to be grouped to be referenced later), use a FightGroup


TakeUnit: adds a unit to the group. Use this to put your units into the group one by one.

TakeAllUnits: if you want to combine 2 groups, use this class member. If will move the units from one group to the other. Be careful: a unit can only belong to 1 group at any time. After using TakeAllUnits the old group is void & invalid.

SetDeleteWhenEmpty: can be used to delete the group after all its units are removed, for example: after using TakeAllUnits, or after they all been killed.

SetTargCount: is to be used together with RecordVehReinforceGroup; see the earlier post about groups. It can be used several times: each time you add one more targetcount to the reinforce-list. In this manner you can set up a FightGroup to be reinforced automaticly with different types of units.

ClearTargCount: removes any and all targetcounts set by SetTargCount. Since there's no way of removing a specific targetcount (in case you've added multiple, see above), the only way to remove 1 of them, is to use this class member, and adding the targetcounts again.

RemoveUnit: remove a single unit from the group. The unit will no longer execute the group actions.

GetFirstOfType: This is rather complicated. It returns the first unit it finds in the group, that meet your classifications. It returns false (0) when no such unit can be found, or when all units have been returned already.

SetRect: (included in both BuildingGroup and FightGroup) sets a Rect where your vehicles will go to, when they are not performing any actions. For a buildingGroup this is where your convecs will go to when they are waiting for the next kit to be done, for fightergroups, when there's no more enemies to attack. This Rect is ALSO included in MiningGroups, but it is set with the Setup class member, rather than Setrect. Also, for mining groups, you cannot change this rect after the group has been initialised other then initialising it again. For the other group types, you can always change the rect.


SetAttackType: used to attack a certain type of building. You can only supply military buildings, eg. an agridome or residence will result in your units not doing anything. Use together with DoAttackEnemy.

SetCombineFire: units will try to combine their firepower onto a target.

DoAttackEnemy: will attack ANY and ALL units set as 'enemy'. It will only target military units/buildings. This means agridomes and residences will be left alone. The group will go towards the closest enemy unit, even if they are found on the other side of the map, in the dark, with their lights off!

DoAttackUnit: sets a single target to be attacked (SetTargetUnit). The group will not seek out enemies, but will fire on enemy units when they come into range/view.

DoExitMap: will make the group drive to the side of the map, after which they will dissapear.

DoGuardGroup/DoGuardUnit:' will guard the target group (SetTargetGroup/SetTargetUnit). Use together with SetFollowMode.

SetPatrolMode: uses the waypoints structure to set up a list of waypoints for a group to patrol. The group will NOT seek out enemies, but will fire if they come into range/view. Use DoPatrolOnly to initiate patrol, and ClearPatrolMode to stop the units patrolling. They will return to the SetRect region.

DoGuardRect: units will move around the guarded rects -that can be added/removed with AddGuardedRect and ClearGuarderdRects.

IMPORTANT: After DoGuardRect you should call SetRect to supply the rect to be guarded!


I hope this clears out some questions.

If some thing are unclear, let me know. I did not test ANY of the members above while writing this, all is done by memory, so i might have some mix-ups

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:

  • 1. 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;
  • 2. 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;
  • 3. 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;
  • 4. char const *triggerFunction: This is the function that is called when the trigger is fired, in this case: the 'time' is reached.

The items marked in bold 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:


SCRIPT_API void Attack()

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...

Trigger function It fires when...
CreateBuildingCountTrigger a certain number of buildings is reached
CreateVehicleCountTrigger a certain number of vehicles is reached
CreateCountTrigger a certain number of units is reached
CreateAttackedTrigger a unit is attacked (not functioning)
CreateDamagedTrigger a certain part of a group is killed
CreateEscapeTrigger a unit escapes to a given region
CreateKitTrigger a certain kit is ready
CreateMidasTrigger midas condition is reached
CreateOnePlayerLeftTrigger uhm.. make a guess
CreateOperationalTrigger a certain number of buildings is operational
CreatePointTrigger any unit moves over a point
CreateRectTrigger any unit moves into a region
CreateResearchTrigger some research is completed
CreateResourceTrigger a certain amount of resources is reached
CreateEvacTrigger unknown/untested at the moment
  • 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!
  • 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

Examples

AI Coding Examples

Personal tools