Advanced Strategic Command
tactics.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  tactics.cpp - description
3  -------------------
4  begin : Fri Mar 30 2001
5  copyright : (C) 2001 by Martin Bickel
6  email : bickel@asc-hq.org
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "ai_common.h"
19 #include "../actions/attackcommand.h"
20 #include "../actions/moveunitcommand.h"
21 
23 
24 
25 void AI :: searchTargets ( Vehicle* veh, const MapCoordinate3D& pos, TargetVector& tl, int moveDist, AStar3D& vm, int hemmingBonus )
26 {
27 
28  npush ( veh->xpos );
29  npush ( veh->ypos );
30  npush ( veh->height );
31 
32  bool unitRadarActivated = getMap()->getField(veh->getPosition())->unitHere(veh);
33  if ( unitRadarActivated )
34  veh->removeview();
35  // int fieldsWithChangedVisibility = evaluateviewcalculation ( getMap(), veh->xpos, veh->ypos, veh->typ->view, 0xff );
36  veh->xpos = pos.x;
37  veh->ypos = pos.y;
38  veh->height = pos.getBitmappedHeight();
39  veh->addview();
40  int fieldsWithChangedVisibility = evaluateviewcalculation ( getMap(), veh->getPosition(), veh->view, 0xff );
41 
42 
43  if ( AttackCommand::avail ( veh )) {
44  AttackCommand va ( veh );
45  va.searchTargets();
46  for ( AttackCommand::FieldList::const_iterator i = va.getAttackableUnits().begin(); i != va.getAttackableUnits().end(); ++i ) {
47  MapCoordinate targ = i->first;
48  const AttackWeap* aw = &(i->second);
49 
50  int bestweap = -1;
51  int targdamage = -1;
52  int weapstrength = -1;
53  for ( int w = 0; w < aw->count; w++ ) {
54  tunitattacksunit uau ( veh, getMap()->getField (targ)->vehicle, 1, aw->num[w] );
55  uau.calc();
56  if ( uau.dv.damage > targdamage ) {
57  bestweap = aw->num[w];
58  targdamage = uau.dv.damage;
59  weapstrength = aw->strength[w];
60  } else
61  if ( uau.dv.damage == 100 )
62  if ( weapstrength == -1 || weapstrength > aw->strength[w] ) {
63  bestweap = aw->num[w];
64  targdamage = uau.dv.damage;
65  weapstrength = aw->strength[w];
66  }
67  }
68 
69  if ( bestweap == -1 )
70  displaymessage ( "inconsistency in AI :: searchTarget", 1 );
71 
72 
73  MoveVariant* mv = new MoveVariant;
74 
75  tunitattacksunit uau ( veh, getMap()->getField(targ)->vehicle, 1, bestweap );
76  mv->orgDamage = uau.av.damage;
77  mv->damageAfterMove = uau.av.damage;
78  mv->enemyOrgDamage = uau.dv.damage;
79  uau.calc();
80 
81 
82  mv->damageAfterAttack = uau.av.damage;
83  mv->enemyDamage = uau.dv.damage;
84  mv->enemy = getMap()->getField(targ)->vehicle;
85  mv->movePos = pos;
86  mv->attackx = targ.x;
87  mv->attacky = targ.y;
88  mv->weapNum = bestweap;
89  mv->moveDist = moveDist;
90  mv->attacker = veh;
91 
92  mv->positionThreat = getFieldThreat ( pos.x, pos.y ).threat[veh->getValueType()];
93 
94 
95  for ( int nf = 0; nf < sidenum; nf++ ) {
96  MapCoordinate mc = getNeighbouringFieldCoordinate ( MapCoordinate ( mv->attackx, mv->attacky), nf );
97  MapField* fld = getMap()->getField(mc);
98  if ( fld && !veh->typ->wait)
99  mv->neighbouringFieldsReachable[nf] = (vm.visited.find( MapCoordinate3D(mc.x, mc.y, pos.getBitmappedHeight()) ) || ( veh->xpos == mc.x && veh->ypos == mc.y )) && !fld->building && (!fld->vehicle || fld->unitHere(veh));
100  else
101  mv->neighbouringFieldsReachable[nf] = false;
102 
103  }
104 
105  int attackerDirection = getdirection ( targ.x, targ.y, pos.x, pos.y );
106  float hemmingFactor = 1;
107  for ( int nf = 0; nf < sidenum-1 && nf < hemmingBonus; nf++ ) {
108  // we are starting opposite the attacker, since this is the highest hemming bonus
109  int checkDir;
110  if ( nf == 0 )
111  checkDir = attackerDirection+sidenum/2;
112  else {
113  if ( nf & 1 )
114  checkDir = attackerDirection+sidenum/2 + (nf+1)/2;
115  else
116  checkDir = attackerDirection+sidenum/2 - (nf+1)/2;
117  }
118 
119  MapCoordinate mc = getNeighbouringFieldCoordinate ( MapCoordinate ( mv->attackx, mv->attacky), checkDir%sidenum );
120  MapField* fld = getMap()->getField(mc);
121  if ( fld && !fld->building && !fld->vehicle )
122  hemmingFactor += AttackFormula::getHemmingFactor ( nf );
123  }
124 
125  mv->result = getAttackValue ( uau, mv->attacker, mv->enemy, hemmingFactor );
126 
127  if ( mv->result > 0 )
128  tl.push_back ( mv );
129 
130  }
131  }
132 
133  // if ( fieldsWithChangedVisibility )
134  // evaluateviewcalculation ( getMap(), veh->xpos, veh->ypos, veh->typ->view, 0xff );
135  veh->removeview();
136  npop ( veh->height );
137  npop ( veh->ypos );
138  npop ( veh->xpos );
139 
140  if ( unitRadarActivated )
141  veh->addview();
142 
143  if ( fieldsWithChangedVisibility || 1 ) // viewbug.sav !!!!!
144  evaluateviewcalculation ( getMap(), veh->getPosition(), veh->view, 0xff );
145 }
146 
148 {
149  return ( result > mv2.result
150  || (result == mv2.result && positionThreat < mv2.positionThreat )
151  || (result == mv2.result && positionThreat == mv2.positionThreat && moveDist < mv2.moveDist) ); // mv1.moveDist < mv2.moveDist
152 }
153 
154 
156 {
157  return ( result < mv2.result
158  || (result == mv2.result && positionThreat > mv2.positionThreat )
159  || (result == mv2.result && positionThreat == mv2.positionThreat && moveDist > mv2.moveDist) ); // mv1.moveDist < mv2.moveDist
160 }
161 
162 /*
163 bool operator> ( const AI::MoveVariant& mv1, const AI::MoveVariant& mv2 )
164 {
165  return ( mv1.result > mv2.result
166  || (mv1.result == mv2.result && mv1.positionThreat < mv2.positionThreat )
167  || (mv1.result == mv2.result && mv1.positionThreat == mv2.positionThreat && mv1.moveDist < mv2.moveDist) ); // mv1.moveDist < mv2.moveDist
168 }
169 
170 bool operator< ( const AI::MoveVariant& mv1, const AI::MoveVariant& mv2 )
171 {
172  return ( mv1.result < mv2.result
173  || (mv1.result == mv2.result && mv1.positionThreat > mv2.positionThreat )
174  || (mv1.result == mv2.result && mv1.positionThreat == mv2.positionThreat && mv1.moveDist > mv2.moveDist) ); // mv1.moveDist < mv2.moveDist
175 }
176 
177 */
178 bool AI::moveVariantComp ( const AI::MoveVariant* mv1, const AI::MoveVariant* mv2 )
179 {
180  return *mv1 < *mv2;
181  // return ( mv1->result < mv2->result || (mv1->result == mv2->result && mv1->moveDist > mv2->moveDist ));
182 }
183 
184 void AI::getAttacks ( AStar3D& vm, Vehicle* veh, TargetVector& tv, int hemmingBonus, bool justOne, bool executeService )
185 {
186 
188 
189  int x1 = veh->xpos;
190  int y1 = veh->ypos;
191  int x2 = veh->xpos;
192  int y2 = veh->ypos;
193 
194  for ( AStar3D::VisitedContainer::iterator ff = vm.visited.begin(); ff != vm.visited.end(); ++ff ) {
195  AStar3D::Node n = *ff;
196  x1 = min ( x1, n.h.x );
197  y1 = min ( y1, n.h.y );
198  x2 = max ( x2, n.h.x );
199  y2 = max ( y2, n.h.y );
200  }
201 
202  int maxrange = 0;
203  for ( int i = 0; i < veh->typ->weapons.count; ++i )
204  maxrange = max ( maxrange, veh->typ->weapons.weapon[i].maxdistance );
205 
206  maxrange += 30;
207 
208  x1 = max ( 0, x1 - maxrange/10 );
209  y1 = max ( 0, y1 - maxrange*2/10 );
210  x2 = min ( getMap()->xsize, x2 + maxrange/10 );
211  y2 = min ( getMap()->ysize, y2 + maxrange*2/10 );
212 
213  int enemycount = 0;
214  for ( int y = y1; y <= y2 && !enemycount; ++y)
215  for ( int x = x1; x <= x2; ++x) {
216  MapField* fld = getMap()->getField(x,y);
217  if ( fld && fld->vehicle)
218  if ( getPlayer(fld->vehicle->getOwner()).diplomacy.isHostile( getPlayerNum() ) )
219  enemycount++;
220  }
221 
222  if( !enemycount )
223  return;
224 
225 
226 
227  int orgxpos = veh->xpos ;
228  int orgypos = veh->ypos ;
229 
230  if ( getMap()->getField ( veh->xpos, veh->ypos )->unitHere ( veh ) ) // unit not inside a building or transport
231  searchTargets ( veh, veh->getPosition3D(), tv, 0, vm, hemmingBonus );
232 
233  if ( tv.size() && justOne )
234  return;
235 
236  // Now we cycle through all fields that are reachable...
237  if ( !veh->typ->wait ) {
238 
239  RefuelConstraint* apl = NULL;
240  if ( RefuelConstraint::necessary ( veh, *this ))
241  apl = new RefuelConstraint( *this, veh );
242 
243  int fuelLacking = 0;
244  for ( AStar3D::VisitedContainer::iterator ff = vm.visited.begin(); ff != vm.visited.end(); ++ff ) {
245  AStar3D::Node node = *ff;
246  if ( !node.hasAttacked ) {
247  MapField* fld = getMap()->getField (node.h);
248  if ( !fld->vehicle && !fld->building ) {
249  if ( !apl || apl->returnFromPositionPossible ( node.h )) {
250  searchTargets ( veh, node.h, tv, beeline ( node.h.x, node.h.y, orgxpos, orgypos ), vm, hemmingBonus );
251  if ( tv.size() && justOne )
252  return;
253  } else
254  fuelLacking++;
255  }
256  }
257  }
258 
259  if ( apl ) {
260  delete apl;
261  apl = NULL;
262  }
263 
264  if ( !tv.size() && fuelLacking && executeService)
265  issueRefuelOrder( veh, true );
266  }
267 }
268 
269 AI::AiResult AI::executeMoveAttack ( Vehicle* veh, TargetVector& tv )
270 {
271  int unitNetworkID = veh->networkid;
272  AiResult result;
273 
274  MoveVariant* mv = *max_element( tv.begin(), tv.end(), moveVariantComp );
275 
276  int targetID = mv->enemy->networkid;
277 
278  if ( mv->movePos != veh->getPosition3D() ) {
279  VisibilityStates org_vision = getVision() ;
281 
282  auto_ptr<MoveUnitCommand> muc ( new MoveUnitCommand( veh ));
283  muc->setFlags ( MoveUnitCommand::NoInterrupt );
284  muc->setDestination( mv->movePos );
285  ActionResult res = muc->execute( getContext () );
286  if ( res.successful() )
287  muc.release();
288  else {
289  displaymessage ( "AI :: executeMoveAttack \n error in movement with unit %d", 1, veh->networkid );
290  }
291 
292  result.unitsMoved ++;
293  setVision(org_vision);
294  }
295 
296  // the unit may have been shot down due to reactionfire during movement
297  if ( !getMap()->getUnit(unitNetworkID)) {
298  result.unitsDestroyed++;
299  return result;
300  }
301 
302  if ( veh->attacked )
303  return result;
304 
305  if ( !getMap()->getUnit( targetID )) {
306  // can happen if the unit that was selected for being attacked
307  // had reactionfire enabled and Kamikaze, which destroyed it.
308  return result;
309  }
310 
311  auto_ptr<AttackCommand> va (new AttackCommand( veh ));
312  ActionResult res = va->searchTargets();
313  if ( !res.successful() )
314  displaymessage ( "AI :: executeMoveAttack \n error in attack step 2 with unit %d", 1, veh->networkid );
315 
316  VehicleTypeEfficiencyCalculator vtec( *this, veh, mv->enemy );
317 
318  va->setTarget( MapCoordinate( mv->attackx, mv->attacky), mv->weapNum );
319  res = va->execute( getContext() );
320  if ( !res.successful() )
321  displaymessage ( "AI :: executeMoveAttack \n error in attack step 3 with unit %d", 1, veh->networkid );
322  else
323  va.release();
324 
325  vtec.calc();
326 
327  result.unitsMoved ++;
328 
329  // the unit may have been shot in the attack
330  if ( !getMap()->getUnit(unitNetworkID)) {
331  result.unitsDestroyed++;
332  return result;
333  }
334 
335 
336  if ( MoveUnitCommand::avail( veh ))
337  result += moveToSavePlace ( veh );
338 
339  return result;
340 }
341 
342 bool AI::targetsNear( Vehicle* veh )
343 {
344  AStar3D ast ( getMap(), veh, false, veh->getMovement() );
346  TargetVector tv;
347  getAttacks ( ast, veh, tv, 0, true, false );
348  if ( tv.size() )
349  return true;
350  else
351  return false;
352 }
353 
354 
355 int AI::getDirForBestTacticsMove ( const Vehicle* veh, TargetVector& tv )
356 {
357  if ( tv.size() <= 0 )
358  return -1;
359 
360  MoveVariant* mv = *max_element( tv.begin(), tv.end(), moveVariantComp );
361  return getdirection ( veh->xpos, veh->ypos, mv->movePos.x, mv->movePos.y );
362 }
363 
364 MapCoordinate AI::getDestination ( Vehicle* veh )
365 {
366  AiParameter::Task task = veh->aiparam[ getPlayerNum() ]->getTask();
367  if ( task == AiParameter::tsk_nothing || task == AiParameter::tsk_tactics ) {
368  TargetVector tv;
369  AStar3D ast ( getMap(), veh, false, veh->getMovement() );
371  getAttacks ( ast, veh, tv, 0 );
372 
373  if ( tv.size() > 0 ) {
374 
375  MoveVariant* mv = *max_element( tv.begin(), tv.end(), moveVariantComp );
376  return MapCoordinate ( mv->movePos.x, mv->movePos.y );
377  } else
379  }
380 
381  if ( task == AiParameter::tsk_strategy ) {
382  Section* sec = sections.getBest ( 0, veh );
383  if ( sec )
384  return MapCoordinate ( sec->centerx, sec->centery );
385  }
386 
387  return MapCoordinate ( veh->xpos, veh->ypos );
388 }
389 
390 
391 AI::AiResult AI::moveToSavePlace ( Vehicle* veh, int preferredHeight )
392 {
393  int unitNetworkID = veh->networkid;
394 
395  AiResult result;
396 
397  auto_ptr<MoveUnitCommand> muc( new MoveUnitCommand( veh ));
398 
399  ActionResult res = muc->searchFields( preferredHeight );
400  if ( !res.successful() )
401  return result;
402 
403  const set<MapCoordinate3D>& fields = muc->getReachableFields();
404 
405  if ( fields.empty() )
406  return result;
407 
408 
409  int xtogo = veh->xpos;
410  int ytogo = veh->ypos;
411  int threat = maxint;
412  int dist = maxint;
413 
414  if ( getMap()->getField ( veh->xpos, veh->ypos)->unitHere ( veh ) ) { // vehicle not in building / transport
415  threat = int( getFieldThreat ( veh->xpos, veh->ypos).threat[ veh->aiparam[ getPlayerNum()]->valueType] * 1.5 + 1);
416  // multiplying with 1.5 to make this field a bit unattractive, to allow other units (for example buggies) to attack from this field to, since it is probably quite a good position (else it would not have been chosen)
417  }
418 
419  for ( set<MapCoordinate3D>::const_iterator i = fields.begin(); i != fields.end(); ++i ) {
420  MapField* fld = getMap()->getField( *i );
421  if ( !fld->vehicle && !fld->building ) {
422  AiThreat& ait = getFieldThreat ( i->x, i->y );
423  int _dist = beeline ( i->x, i->y, veh->xpos, veh->ypos);
424 
425  // make fields far away a bit unattractive; we don't want to move the whole distance back again next turn
426  int t = int( ait.threat[ veh->aiparam[ getPlayerNum()]->valueType ] * log ( double(_dist) )/log(double(10)) );
427 
428  if ( t < threat || ( t == threat && (_dist < dist ))) {
429  threat = t;
430  xtogo = i->x;
431  ytogo = i->y;
432  dist = _dist;
433  }
434  }
435  }
436 
437  if ( veh->xpos != xtogo || veh->ypos != ytogo ) {
438 
439  muc->setDestination( MapCoordinate(xtogo, ytogo ));
440 
441  res = muc->execute( getContext() );
442  if ( res.successful() )
443  muc.release();
444  else
445  displaymessage ( "AI :: moveToSavePlace \n error in movement3 step 3 with unit %d", 1, veh->networkid );
446 
447  result.unitsMoved++;
448  }
449 
450  if ( !getMap()->getUnit( unitNetworkID ))
451  result.unitsDestroyed++;
452 
453  return result;
454 }
455 
456 
457 int AI::changeVehicleHeight ( Vehicle* veh, int preferredDirection )
458 {
459 #if 0 // ####
460  int bh = getBestHeight ( veh );
461  if ( bh != veh->height && bh != -1 ) {
462  ChangeVehicleHeight* cvh;
463  int newheight;
464  if ( bh > veh->height ) {
465  cvh = new IncreaseVehicleHeight ( mapDisplay, NULL );
466  newheight = veh->height << 1;
467  } else {
468  cvh = new DecreaseVehicleHeight ( mapDisplay, NULL );
469  newheight = veh->height >> 1;
470  }
471  auto_ptr<ChangeVehicleHeight> acvh ( cvh );
472 
473  if ( newheight & veh->typ->height ) {
474  if ( cvh->available ( veh ) ) {
475 
476  int stat = cvh->execute ( veh, -1, -1, 0, newheight, 1 );
477  if ( stat == 2 ) { // if the unit could change its height vertically, or the height change is not available, skip this block
478 
479  int bestx = -1;
480  int besty = -1;
481  int moveremain = minint;
482 
483  if ( preferredDirection == -1 ) {
484  /*
485  // making a backup
486  TemporaryContainerStorage tus ( veh );
487  veh->height = newheight;
488  veh->resetMovement ( );
489  MapCoordinate mc = getDestination ( veh );
490  preferredDirection = getdirection ( veh->xpos, veh->ypos, mc.x, mc.y );
491  tus.restore();
492  */
493  preferredDirection = getdirection ( veh->xpos, veh->ypos, getMap()->xsize/2, getMap()->ysize/2 );
494  }
495 
496  for ( int i = 0; i < cvh->reachableFields.getFieldNum(); i++ ) {
497  int newMoveRemain = cvh->reachableFields.getData ( i ).dist;
498 
499  // check if we move in the direction that should be moved to
500  if ( preferredDirection != -1 ) {
501  int xp, yp;
502  cvh->reachableFields.getFieldCoordinates ( i, &xp, &yp );
503 
504  int dir = preferredDirection - getdirection ( veh->xpos, veh->ypos, xp, yp );
505  if ( dir > sidenum/2 )
506  dir -= sidenum;
507  if ( dir < -sidenum/2 )
508  dir += sidenum;
509 
510  dir = abs ( dir );
511  // ok, now we have the directional difference in dir, with a range of 0 .. 3 ; 0 meaning moving in the preferred direction
512 
513 
514  newMoveRemain -= (dir*2-3)* veh->typ->steigung*maxmalq / 3;
515  // if dir==0, we subtract -steigung => newMoveRemain is getting larger because we move in the right direction
516  // if dir==3, we subtract +steigung
517  }
518 
519  if ( newMoveRemain > moveremain ) {
520  cvh->reachableFields.getFieldCoordinates ( i, &bestx, &besty );
521  moveremain = newMoveRemain;
522  }
523  }
524 
525  if ( bestx != -1 && besty != -1 ) {
526  cvh->execute ( NULL, bestx, besty, 2, -1, -1 );
527  if ( cvh->getStatus() == 1000 )
528  return 1;
529 
530  if ( cvh->getStatus() != 3 )
531  displaymessage ( "AI :: changeVehicleHeight \n error in changeHeight step 2 with unit %d", 1, veh->networkid );
532 
533  cvh->execute ( NULL, bestx, besty, 3, -1, -1 );
534  if ( cvh->getStatus() != 1000 )
535  displaymessage ( "AI :: changeVehicleHeight \n error in changeHeight step 3 with unit %d", 1, veh->networkid );
536 
537  return 1;
538  } else
539  return -1;
540  } else {
541  if ( stat == 1000 )
542  return 1;
543 
544  if ( veh->typ->steigung == 0 )
545  return -2;
546 
547  return -1;
548  }
549  } else { // cvh->available
550  return -2;
551  }
552  }
553 
554  }
555  #endif
556  return 0;
557 }
558 
559 
560 AI::AiResult AI::tactics( void )
561 {
562  const int tsk_num = 3;
563  AiParameter::Task tasks[tsk_num] = { AiParameter::tsk_nothing,
566  };
567  AiResult result;
568 
569  displaymessage2("starting tactics ... ");
570 
571  typedef list<int> TactVehicles;
572  TactVehicles tactVehicles;
573 
574  for ( Player::VehicleList::iterator vi = getPlayer().vehicleList.begin(); vi != getPlayer().vehicleList.end(); vi++ ) {
575  Vehicle* veh = *vi;
576 
577  if ( !veh->aiparam[ getPlayerNum() ] )
578  calculateThreat( veh );
579 
580  bool unitUsable = false;
583  || (veh->aiparam[ getPlayerNum() ]->getJob() == AiParameter::job_guard && veh->aiparam[ getPlayerNum() ]->dest_nwid == -1 && veh->aiparam[ getPlayerNum() ]->dest.x == -1 ))
584  for ( int j = 0; j < tsk_num; j++ )
585  if ( veh->aiparam[ getPlayerNum() ]->getTask() == tasks[j] )
586  unitUsable = true;
587 
588  if ( getMap()->getField(veh->getPosition())->vehicle != veh ) {
589  Vehicle* transport = getMap()->getField(veh->getPosition())->vehicle;
590  if ( transport ) {
591  const ContainerBaseType::TransportationIO* unloadSystem = transport->vehicleUnloadSystem( veh->typ, -1 );
592  if ( unloadSystem && unloadSystem->disableAttack )
593  continue;
594  }
595  }
596 
597 
598  int maxWeapDist = minint;
599  for ( int w = 0; w < veh->typ->weapons.count; w++ )
600  if ( veh->typ->weapons.weapon[w].shootable() )
601  maxWeapDist = max ( veh->typ->weapons.weapon[w].maxdistance , maxWeapDist );
602 
603  int maxMove = minint;
604  for ( int h = 0; h < 8; h++ )
605  if ( veh->typ->height & ( 1 << h ))
606  maxMove = max ( veh->typ->movement[h], maxMove );
607 
608  if ( maxWeapDist > 0 ) {
609  bool enemiesNear = false;
610  int ydist = (maxMove + maxWeapDist) / maxmalq * 2;
611  int xdist = ydist / 4;
612  for ( int x = veh->xpos - xdist; x <= veh->xpos + xdist; x++ )
613  for ( int y = veh->ypos - ydist; y <= veh->ypos + ydist; y++ ) {
614  MapField* fld = getMap()->getField(x,y );
615  if ( fld ) {
616  if ( fld->vehicle && getPlayer(veh->getOwner()).diplomacy.isHostile( fld->vehicle->getOwner() ) )
617  enemiesNear = true;
618  if ( fld->building && getPlayer(veh->getOwner()).diplomacy.isHostile( fld->building->getOwner() ) )
619  enemiesNear = true;
620  }
621  }
622 
623  if ( unitUsable && enemiesNear)
624  tactVehicles.push_back ( veh->networkid );
625  }
626  }
627 
628  int hemmingBonus = 5;
629 
630  size_t lastTactVehiclesSize; // = tactVehicles.size();
631 
632  while ( !tactVehicles.empty() ) {
633  lastTactVehiclesSize = tactVehicles.size();
634 
635  typedef map<int, MoveVariantContainer> Targets;
636  Targets targets;
637 
638  int directAttackNum;
639  do {
640  directAttackNum = 0;
641  for ( TactVehicles::iterator i = tactVehicles.begin(); i != tactVehicles.end(); ) {
642  Vehicle* veh = getMap()->getUnit( *i );
643  if ( veh ) {
644 
645  unitCounter++;
646  displaymessage2("tact: processing operation %d", unitCounter );
647  checkKeys();
648 
649  int stat = changeVehicleHeight ( veh, -1 );
650 
651  if ( !getMap()->getUnit(*i) )
652  continue;
653 
654  if ( stat == -1 ) { // couldn't change height due to blocked way or something similar
656  result.unitsWaiting++;
657  i++;
658  } else {
659 
660  AStar3D ast ( getMap(), veh, false, veh->getMovement() );
662  TargetVector tv;
663  getAttacks ( ast, veh, tv, hemmingBonus );
664 
665  if ( !getMap()->getUnit(*i) )
666  continue;
667 
668  if ( tv.size() ) {
669  MoveVariant* mv = *max_element( tv.begin(), tv.end(), moveVariantComp );
670 
671  // airplane landing constraints
672 
673  bool directAttack = false;
674  if ( beeline ( mv->movePos.x, mv->movePos.y, mv->attackx, mv->attacky ) > maxmalq || veh->height >= chtieffliegend || (mv->enemy && mv->enemy->height >= chtieffliegend) )
675  directAttack = true;
676 
677  int freeNeighbouringFields = 0;
678  for ( int j = 0; j < sidenum; j++ ) {
679  MapField* fld = getMap()->getField ( getNeighbouringFieldCoordinate ( MapCoordinate(mv->attackx, mv->attacky), j));
680  if ( fld )
681  if ( !fld->building && !fld->vehicle )
682  freeNeighbouringFields++;
683  }
684 
685  if ( freeNeighbouringFields <= 1 )
686  directAttack = true;
687 
688  if ( mv->enemyDamage >= 100 )
689  directAttack = true;
690 
691  if ( directAttack ) {
692  /* to avoid recalculating the vision with every variant we are going to make it
693  easy...
694  Since all units now only attack the neighbouring fields, which is generally
695  visible, the AI does not cheat here.
696  */
697  VisibilityStates org_vision = getVision();
699 
700  AiResult res = executeMoveAttack ( veh, tv );
701  i = tactVehicles.erase ( i );
702 
703  if ( res.unitsMoved )
704  unitsWorkedInTactics.insert(veh);
705 
706  if ( !res.unitsDestroyed )
708 
709  result += res;
710 
711  directAttackNum++;
712 
713  setVision(org_vision);
714 
715  } else {
716  targets[mv->enemy->networkid].push_back( *mv );
717  i++;
718  }
719  } else {
720  // there is nothing the unit can do in tactics mode
721  if ( veh->aiparam[ getPlayerNum() ]->getTask() != AiParameter::tsk_serviceRetreat )
722  veh->aiparam[ getPlayerNum() ]->resetTask();
723  i = tactVehicles.erase ( i );
724  }
725  }
726  } else {
727  // unit not available any more, get next one
728  i = tactVehicles.erase ( i );
729 
730  }
731  }
732 
733  // if there were direct attacks, some fields may now be blocked by units that did a directAttack,
734  // so all other units plans must be recalculated
735 
736  if ( directAttackNum )
737  targets.clear();
738 
739  } while ( directAttackNum );
740 
741  if ( !targets.empty() ) {
742  // we are beginning with the targets that the most own units want to attack
743  Targets::iterator currentTarget = targets.begin();
744  for ( Targets::iterator i = targets.begin(); i != targets.end(); i++ )
745  if ( i->second.size() > currentTarget->second.size())
746  currentTarget = i;
747 
748  if ( currentTarget->second.size()-1 < hemmingBonus )
749  hemmingBonus = currentTarget->second.size()-1;
750 
751  typedef list<MapCoordinate> AffectedFields;
752  AffectedFields affectedFields;
753 
754  /* to avoid recalculating the vision with every variant we are going to make it
755  easy...
756  Since all units now only attack the neighbouring fields, which is generally
757  visible, the AI does not cheat here.
758  */
759  VisibilityStates org_vision = getVision();
761 
762  /* we don't need to discard all the calculations made above after a single attack.
763  Only the attacks that are near enough to be affected by the last movement and attack
764  will be ignored and recalculated in the next pass
765  */
766  do { // while currentTarget != targets.end()
767 
768  Vehicle* enemy = getMap()->getUnit( currentTarget->first);
769  // the enemy may have been shot down by a direct attack earlier
770  if ( enemy ) {
771  affectedFields.push_back ( MapCoordinate(enemy->xpos, enemy->ypos) );
772 
773  MoveVariantContainer attacker = currentTarget->second;
774 
775  // use the most effective units first
776  sort( attacker.begin(), attacker.end() );
777 
778  MoveVariantContainer::iterator mvci = attacker.begin();
779  Vehicle* positions[sidenum];
780  Vehicle* finalPositions[sidenum];
781  for ( int i = 0; i< sidenum; i++ ) {
782  positions[i] = NULL;
783  finalPositions[i] = NULL;
784  }
785  float finalValue = 0;
786 
787  tactics_findBestAttackUnits ( attacker, mvci, positions, 0, finalPositions, finalValue, 0, 0, ticker );
788 
789  if ( finalValue > 0 ) {
790  for ( int i = 0; i < sidenum; i++ )
791  if ( finalPositions[i] ) {
792  int nwid = finalPositions[i]->networkid;
793  setVision(org_vision);
794  MapCoordinate affected = MapCoordinate(finalPositions[i]->xpos, finalPositions[i]->ypos);
795  MapCoordinate3D dst = getNeighbouringFieldCoordinate( MapCoordinate3D( enemy->xpos, enemy->ypos, finalPositions[i]->height ), i);
796  dst.setnum ( dst.x, dst.y, -2 );
797 
798  moveUnit ( finalPositions[i], dst );
800 
801  affectedFields.push_back ( affected );
802  // the unit may have been shot down due to reaction fire
803 
804  if ( !getMap()->getUnit ( nwid ) ) {
805  TactVehicles::iterator att = find ( tactVehicles.begin(), tactVehicles.end(), nwid ) ;
806  tactVehicles.erase ( att );
807  finalPositions[i] = NULL;
808  }
809  }
810 
811 
812  int unitcount = 0;
813  for ( int i = 0; i < sidenum; i++ )
814  if ( finalPositions[i] )
815  unitcount++;
816 
817  if ( unitcount ) {
818  int attackOrder[sidenum];
819  int finalOrder[sidenum];
820  for ( int i = 0; i< sidenum; i++ )
821  attackOrder[i] = finalOrder[i] = -1;
822 
823 
824  int finalDamage = -1;
825  int finalAttackNum = maxint;
826  tactics_findBestAttackOrder ( finalPositions, attackOrder, enemy, 0, enemy->damage, finalDamage, finalOrder, finalAttackNum );
827 
828  vector<Vehicle*> unitsToMoveAwayAfterAttacking;
829 
830  MapField* enemyField = getMap()->getField(enemy->xpos, enemy->ypos);
831  for ( int i = 0; i < finalAttackNum && enemyField->vehicle == enemy && finalAttackNum < maxint; i++ ) {
832  checkKeys();
833  if ( finalOrder[i] < 0 )
834  warningMessage("!!!");
835  // if ( i+1 < finalAttackNum ) {
836  if ( i < finalAttackNum && finalPositions[finalOrder[i]] ) {
837  Vehicle* a = finalPositions[finalOrder[i]];
838  if ( finalOrder[i] < 0 )
839  warningMessage("!!!");
840 
841  unitsWorkedInTactics.insert(a);
842 
843 
844  int nwid = a->networkid;
845  auto_ptr<AttackCommand> va ( new AttackCommand ( a ));
846  va->searchTargets();
847 
848  VehicleTypeEfficiencyCalculator vtec (*this, finalPositions[finalOrder[i]], enemy );
849  va->setTarget( enemy->getPosition() );
850  ActionResult res = va->execute( getContext() );
851  if ( !res.successful() && strictChecks )
852  displaymessage("inconsistency #1 in AI::tactics attack", 1 );
853 
854  if ( res.successful() )
855  va.release();
856 
857 
858  vtec.calc();
859 
860  TactVehicles::iterator att = find ( tactVehicles.begin(), tactVehicles.end(), nwid ) ;
861  tactVehicles.erase ( att );
862 
863  if ( getMap()->getUnit( nwid ) && a->typ->hasFunction( ContainerBaseType::MoveAfterAttack ))
864  unitsToMoveAwayAfterAttacking.push_back( a );
865  }
866  }
867 
868  for ( vector<Vehicle*>::iterator i = unitsToMoveAwayAfterAttacking.begin(); i != unitsToMoveAwayAfterAttacking.end(); ++i )
869  moveToSavePlace( *i );
870 
871  } // unitcount > 0
872 
873 
874  } // else { // if finalValue > 0
875 
876  } // if enemy
877 
878  bool processable = true;
879  do {
880  currentTarget++;
881  if ( currentTarget != targets.end() ) {
882  if ( hemmingBonus == currentTarget->second.size()-1 ) {
883  for ( AffectedFields::iterator i = affectedFields.begin(); i != affectedFields.end(); i++ )
884  for ( MoveVariantContainer::iterator j = currentTarget->second.begin(); j != currentTarget->second.end(); j++ ) {
885  Vehicle* veh = j->attacker;
886 
887  // here are only vehicles that attack neighbouring fields; ranged attacks are handled as "directAttacks" earlier
888  if ( beeline ( *i, veh->getPosition()) < veh->maxMovement()+20 )
889  processable = false;
890  }
891  } else
892  processable = false;
893  }
894  } while ( !processable && currentTarget != targets.end() );
895 
896  } while ( currentTarget != targets.end() );
897 
898  setVision(org_vision);
899 
900  } else {
901  // no attacks are possible
902  tactVehicles.clear();
903  }
904  if ( lastTactVehiclesSize == tactVehicles.size() ) {
905  // displaymessage ("AI :: tactics ; escaping infinite loop; please report this error !",1 );
906  return result;
907  }
908 
909  }
910 
911  displaymessage2("tactics completed ... ");
912 
913  return result;
914 }
915 
916 void AI :: tactics_findBestAttackOrder ( Vehicle** units, int* attackOrder, Vehicle* enemy, int depth, int damage, int& finalDamage, int* finalOrder, int& finalAttackNum )
917 {
918  if ( damage > enemy->damage )
919  if ( (depth < finalAttackNum && damage==finalDamage) || damage > finalDamage ) {
920  finalDamage = damage;
921  finalAttackNum = depth;
922  for ( int i = 0; i < sidenum; i++ )
923  finalOrder[i] = attackOrder[i];
924  }
925 
926  if ( damage >= 100 || depth >= 6 )
927  return;
928 
929  for ( int i = 0; i< sidenum; i++ ) {
930  bool found = false;
931  for ( int j = 0; j < depth; j++ )
932  if ( attackOrder[j] == i)
933  found = true;
934  if ( units[i] && !found ) {
935  // attackOrder[3] = 1 means that the unit on position 1 should attack as third
936  attackOrder[depth] = i;
937 
938  npush ( units[i]->xpos );
939  npush ( units[i]->ypos );
941  units[i]->xpos = mc.x;
942  units[i]->ypos = mc.y;
943 
944  tunitattacksunit battle ( units[i], enemy );
945  int newdamage = battle.dv.damage;
946  battle.calc();
947  newdamage = battle.dv.damage - newdamage;
948  npop ( units[i]->ypos );
949  npop ( units[i]->xpos );
950 
951  tactics_findBestAttackOrder ( units, attackOrder, enemy, depth+1, damage+newdamage, finalDamage, finalOrder, finalAttackNum );
952 
953  attackOrder[depth] = -1;
954  }
955  }
956 
957 }
958 
959 
960 int AI :: getValue( Vehicle* v )
961 {
962  if ( v->aiparam[getPlayerNum()] == NULL ) {
963  calculateThreat(v);
964  }
965  return v->aiparam[getPlayerNum()]->getValue();
966 }
967 
968 float AI :: getAttackValue ( const tfight& battle, Vehicle* attackingUnit, Vehicle* attackedUnit, float factor )
969 {
970  calculateThreat( attackedUnit );
971  float result = (battle.dv.damage - attackedUnit->damage) * getValue(attackedUnit) * factor
972  - 1/config.aggressiveness * (battle.av.damage - attackingUnit->damage) * getValue(attackingUnit) ;
973  if ( battle.dv.damage >= 100 )
974  result += attackedUnit->aiparam[getPlayerNum()]->getValue() * attack_unitdestroyed_bonus;
975  return result;
976 }
977 
978 
979 
981  bool neighbours[sidenum];
982  bool checkHemming ( Vehicle* d_eht, int direc )
983  {
984  return neighbours[direc];
985  };
986  public:
987  UnitAttacksUnit_FakeHemming ( AI* ai, Vehicle* attacker, Vehicle* defender, Vehicle** _neighbours ) : tunitattacksunit ( attacker , defender )
988  {
989  for ( int i = 0; i < sidenum; i++ ) {
990  MapField* fld = ai->getMap()->getField ( getNeighbouringFieldCoordinate ( attacker->getPosition(), i ));
991 
992  Vehicle* v = NULL;
993  if ( fld )
994  v = fld->vehicle;
995 
996 
997  if ( v && attackpossible2n ( v, defender ) )
998  neighbours[i] = true;
999  else
1000  neighbours[i] = false;
1001  }
1002 
1003  for ( int i = 0; i < sidenum; i++ ) {
1004  if ( _neighbours[i] == attacker )
1005  break;
1006 
1007  if ( _neighbours[i] )
1008  neighbours[i] = true;
1009  }
1010  setup ( attacker, defender, true, -1 );
1011  };
1012  };
1013 
1014 
1015 
1016 void AI :: tactics_findBestAttackUnits ( const MoveVariantContainer& mvc, MoveVariantContainer::iterator& m, Vehicle** positions, float value, Vehicle** finalPosition, float& finalValue, int unitsPositioned, int recursionDepth, int startTime )
1017 {
1018  if ( m == mvc.end() || unitsPositioned >= 6 || recursionDepth >= 8 || (startTime + config.maxTactTime < ticker && !benchMark)) {
1019  float value = 0;
1020  Vehicle* target = mvc.begin()->enemy;
1021  npush ( target->damage );
1022  for ( int i = 0; i < sidenum && target->damage!=100; i++ )
1023  if ( positions[i] ) {
1024  npush ( positions[i]->ypos );
1025  npush ( positions[i]->xpos );
1026 
1028  positions[i]->xpos = mc.x;
1029  positions[i]->ypos = mc.y;
1030 
1031  UnitAttacksUnit_FakeHemming uau ( this, positions[i], target, positions );
1032  uau.calc();
1033  value += getAttackValue ( uau, positions[i], target );
1034 
1035  npop ( positions[i]->xpos );
1036  npop ( positions[i]->ypos );
1037  }
1038  npop ( target->damage );
1039  if ( value > finalValue ) {
1040  for ( int i = 0; i < sidenum; i++ )
1041  finalPosition[i] = positions[i] ;
1042 
1043  finalValue = value;
1044  }
1045  } else {
1046  for ( int i = 0; i< sidenum; i++ ) {
1047  if ( m->neighbouringFieldsReachable[i] && !positions[i] ) {
1048  positions[i] = m->attacker;
1049  if ( m->result > 0 )
1050  value += m->result;
1051  else
1052  value += 1;
1053  m++;
1054  tactics_findBestAttackUnits ( mvc, m, positions, value, finalPosition, finalValue, unitsPositioned+1, recursionDepth+1, startTime );
1055  m--;
1056  value -= m->result;
1057  positions[i] = NULL;
1058  } else {
1059  m++;
1060  tactics_findBestAttackUnits ( mvc, m, positions, value, finalPosition, finalValue, unitsPositioned, recursionDepth+1, startTime );
1061  m--;
1062  }
1063  }
1064  }
1065 }
1066 
1067 
1068 
1069 bool AI :: vehicleValueComp ( const Vehicle* v1, const Vehicle* v2 )
1070 {
1071  return v1->aiparam[ v1->color/8 ]->getValue() < v2->aiparam[ v1->color/8 ]->getValue();
1072 }
1073 
1074 bool AI :: buildingValueComp ( const Building* v1, const Building* v2 )
1075 {
1076  return v1->aiparam[ v1->color/8 ]->getValue() < v2->aiparam[ v1->color/8 ]->getValue();
1077 }
void findAllAccessibleFields()
searches for all fields that are within the range of maxDist and marks them.
Definition: astar2.cpp:562
bool wait
If the unit cannot attack in the same turn after it has moved, it has to wait.
Definition: vehicletype.h:197
int getValueType(int uheight) const
For the AI: calculating the ValueType if the unit was on the height uheight.
Definition: vehicle.h:389
struct tfight::tavalues dv
int maxMovement() const
the maximum distance that the unit can drive in a single turn on the current level of height ...
Definition: vehicle.cpp:1069
int damage
Damage. 0 is no damage, when damage reaches 100 the container is destroyed.
GameMap * getMap(void)
returns the map this AI runson
Definition: ai.h:486
#define npush(a)
Definition: stack.h:53
the unit will not interrupt the movement if it runs onto a mine of into reaction fire ...
Vehicle * vehicle
Definition: mapfield.h:89
UnitAttacksUnit_FakeHemming(AI *ai, Vehicle *attacker, Vehicle *defender, Vehicle **_neighbours)
Definition: tactics.cpp:987
Structure to store the weapons which a unit can use to perform an attack.
Definition: attack.h:291
struct tfight::tavalues av
AiParameter * aiparam[8]
Definition: vehicle.h:182
static bool moveVariantComp(const AI::MoveVariant *mv1, const AI::MoveVariant *mv2)
Definition: tactics.cpp:178
int moveDist
Definition: ai.h:293
int strength[16]
Definition: attack.h:295
VisitedContainer visited
Definition: astar2.h:122
UnitWeapon weapons
The weapons.
Definition: vehicletype.h:248
bool shootable(void) const
bool hasFunction(ContainerFunctions function) const
int getOwner() const
returns the number of the player this vehicle/building belongs to
bool unitHere(const Vehicle *veh)
checks if the unit is standing on this field. Since units are being cloned for some checks...
Definition: mapfield.cpp:311
VisibilityStates
the different states that a player's view on a field can have
Definition: typen.h:403
void warningMessage(const ASCString &str)
MapCoordinate3D getPosition() const
returns the units position
Definition: vehicle.cpp:1552
storage_t::iterator iterator
Definition: astar2.h:104
SingleWeapon weapon[16]
Definition: vehicletype.h:170
a single field of the map
Definition: mapfield.h:26
Definition: ai.h:44
int threat[aiValueTypeNum]
bool hasAttacked
Definition: astar2.h:69
void calc(void)
Performs the calculation of the attack. The result is only stored in the av and dv structures and is ...
Definition: attack.cpp:178
vector< int > movement
the distance a unit can travel each round. One value for each of the 8 levels of height ...
Definition: vehicletype.h:203
int height
the current level of height ( BITMAPPED ! )
Definition: vehicle.h:118
int beeline(const Vehicle *a, const Vehicle *b)
returns the distance between the units a and b
bool attackpossible2n(const Vehicle *attacker, const Vehicle *target, AttackWeap *atw)
Is attacker able to attack target ? Actual distance used.
Definition: attack.cpp:1026
const int sidenum
the number of sides that a field has; is now fixed at 6;
Definition: typen.h:438
int getPlayerNum(void)
returns the number of the player which is controlled by this ai
Definition: ai.h:490
#define maxmalq
Constants that specify the layout of ASC.
Definition: typen.h:429
MapCoordinate3D getPosition3D() const
returns the units position; if inside building then Height is -1
Definition: vehicle.cpp:1557
friend class RefuelConstraint
Definition: ai.h:168
VehicleList vehicleList
a list of all units
Definition: player.h:135
void setTask(Task t)
void setVision(VisibilityStates v)
Definition: ai.h:508
void log(const Vehicle *attacker, const Vehicle *attackee)
Definition: attack.cpp:417
friend class Section
Definition: ai.h:448
#define chtieffliegend
Definition: typen.h:414
const int attack_unitdestroyed_bonus
Definition: tactics.cpp:22
Coordinate on the twodimensional map.
Definition: typen.h:202
the threat that a unit poses against other units.
Player & getPlayer(void)
Definition: ai.h:492
void displaymessage(const char *formatstring, int num,...)
displays a dialog box with a message
Definition: dlg_box.cpp:1849
float positionThreat
Definition: ai.h:295
int evaluateviewcalculation(GameMap *gamemap, int player_fieldcount_mask, bool disableShareView, const Context *context)
evaluates the view on the whole map.
void setnum(int _x, int _y, int numericalz)
Definition: typen.h:250
void removeview()
removes the units view to the map.
Definition: vehicle.cpp:1006
static bool avail(Vehicle *eht)
int maxdistance
the maximum distance the weapon can shoot
Definition: vehicletype.h:111
int num[16]
Definition: attack.h:296
void resetTask()
Definition: gamemap.cpp:2097
int getValue()
static bool avail(Vehicle *eht)
bool successful() const
friend class VehicleTypeEfficiencyCalculator
Definition: ai.h:410
Vehicle * getUnit(int x, int y, int nwid)
Definition: gamemap.cpp:1215
#define npop(a)
Definition: stack.h:54
VisibilityStates getVision(void)
the AI uses a different vision than human player, to counter the fact that a human player can "know" ...
Definition: ai.h:506
const FieldList & getAttackableUnits()
Definition: attackcommand.h:72
int height
the levels of height which this unit can enter
const ContainerBaseType::TransportationIO * vehicleUnloadSystem(const VehicleType *vehicle, int height)
returns the unloading system
void displaymessage2(const char *formatstring,...)
displays a message in the status line of ASC
int color
The owner of the container.
Definition: attack.h:83
int xpos
the position on the map
Definition: vehicle.h:124
int count
Definition: attack.h:294
int getBitmappedHeight() const
Definition: typen.h:241
const VehicleType * typ
Definition: vehicle.h:83
bool attacked
did the unit already attack this turn
Definition: vehicle.h:109
Coordinate on the map including height.
Definition: typen.h:238
Building * building
Definition: mapfield.h:102
A 3D path finding algorithm, based on the 2D algorithm by Amit J. Patel.
Definition: astar2.h:19
#define maxint
Definition: typen.h:462
bool operator<(const MoveVariant &a) const
Definition: tactics.cpp:155
#define minint
Definition: typen.h:463
An actual building on the map, which references a BuildingType Buildings have an owner,.
Definition: buildings.h:38
const T & max(const T &a, const T &b, const T &c)
Definition: misc.h:97
const T & min(const T &a, const T &b, const T &c)
Definition: misc.h:80
static float getHemmingFactor(int relDir)
Definition: attack.cpp:79
const Node * find(const MapCoordinate3D &pos)
Definition: astar2.h:110
ActionResult searchTargets()
bool operator>(const MoveVariant &a) const
Definition: tactics.cpp:147
MapCoordinate3D dest
int ypos
Definition: vehicle.h:124
int getdirection(const MapCoordinate &start, const MapCoordinate &dest)
int networkid
a unique identification of the unit that is used everywhere in ASC (and not only the network protocol...
Definition: vehicle.h:140
Context getContext()
Definition: base.cpp:597
int getMovement(bool checkFuel=true, bool checkRF=true) const
returns the movement points the unit has left for this turn. CheckFuel should almost always be true...
Definition: vehicle.cpp:558
void addview()
adds the units view to the map.
Definition: vehicle.cpp:990
MapCoordinate3D getNeighbouringFieldCoordinate(const MapCoordinate3D &pos, int direc)
returns the coordinate of the field that is adjecent to the given field in the direction of direc ...
volatile int ticker
Definition: events.cpp:234
float result
Definition: ai.h:292
MapField * getField(int x, int y)
Definition: gamemap.h:465
AiValue * aiparam[8]
Definition: buildings.h:65
MapCoordinate3D h
Definition: astar2.h:64