UFO: Alien Invasion
cp_auto_mission.cpp
Go to the documentation of this file.
1 
6 /*
7 Copyright (C) 2002-2022 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24 
25 #include "../../cl_shared.h"
26 #include "../../cl_inventory.h"
27 #include "cp_auto_mission.h"
28 #include "cp_campaign.h"
29 #include "cp_character.h"
30 #include "cp_geoscape.h"
31 #include "cp_missions.h"
32 #include "cp_mission_triggers.h"
33 #include "../../../shared/mathlib_extra.h"
34 #include "math.h"
35 #include "cp_mission_callbacks.h"
36 
40 typedef enum autoMission_teamType_s {
47 
48 #define MAX_SOLDIERS_AUTOMISSION MAX_TEAMS * AUTOMISSION_TEAM_TYPE_MAX
49 
54 typedef struct autoUnit_s {
55  int idx;
58  double attackStrength;
59  double defendStrength;
60 } autoUnit_t;
61 
65 typedef struct autoMissionBattle_s {
67  short nUnits[AUTOMISSION_TEAM_TYPE_MAX];
68  short actUnits[AUTOMISSION_TEAM_TYPE_MAX];
70  double scoreTeamEquipment[AUTOMISSION_TEAM_TYPE_MAX];
71  double scoreTeamSkill[AUTOMISSION_TEAM_TYPE_MAX];
72  double scoreTeamDifficulty[AUTOMISSION_TEAM_TYPE_MAX];
76  int teamAccomplishment[AUTOMISSION_TEAM_TYPE_MAX];
81 
86 #define SKILL_AWARD_SCALE 0.3f
87 #define ABILITY_AWARD_SCALE 0.06f
88 
89 #define AM_IsPlayer(type) ((type) == AUTOMISSION_TEAM_TYPE_PLAYER)
90 #define AM_IsAlien(type) ((type) == AUTOMISSION_TEAM_TYPE_ALIEN)
91 #define AM_IsCivilian(type) ((type) == AUTOMISSION_TEAM_TYPE_CIVILIAN)
92 #define AM_SetHostile(battle, team, otherTeam, value) (battle)->isHostile[(team)][(otherTeam)] = (value)
93 #define AM_IsHostile(battle, team, otherTeam) ((battle)->isHostile[(team)][(otherTeam)])
94 
95 #define AM_GetUnit(battle, teamIdx, unitIdx) (&battle->units[teamIdx][unitIdx])
96 #define AM_IsUnitActive(unit) (((unit)->chr->HP > 0) && ((unit)->chr->HP > (unit)->chr->STUN))
97 
102 static void AM_ClearBattle (autoMissionBattle_t* battle)
103 {
104  assert(battle != nullptr);
105 
106  OBJZERO(*battle);
107 
108  for (int team = 0; team < AUTOMISSION_TEAM_TYPE_MAX; team++) {
109  battle->scoreTeamDifficulty[team] = 0.5;
110  battle->scoreTeamEquipment[team] = 0.5;
111  battle->scoreTeamSkill[team] = 0.5;
112 
113  for (int otherTeam = 0; otherTeam < AUTOMISSION_TEAM_TYPE_MAX; otherTeam++) {
114  /* If you forget to set this and run a battle, everyone will just kill each other by default */
115  battle->isHostile[team][otherTeam] = true;
116  }
117  }
118 
119  battle->winningTeam = -1;
120  battle->results = nullptr;
121 }
122 
132 static void AM_FillTeamFromAircraft (autoMissionBattle_t* battle, const autoMissionTeamType_t teamNum, const aircraft_t* aircraft, const campaign_t* campaign)
133 {
134  int teamSize;
135  int unitsAlive;
136 
137  assert(teamNum < AUTOMISSION_TEAM_TYPE_MAX);
138  assert(battle != nullptr);
139  assert(aircraft != nullptr);
140 
141  teamSize = 0;
142  unitsAlive = 0;
143  LIST_Foreach(aircraft->acTeam, Employee, employee) {
144  autoUnit_t* unit = AM_GetUnit(battle, teamNum, teamSize);
145 
146  unit->chr = &employee->chr;
147  unit->team = teamNum;
148  unit->idx = teamSize;
149 
150  teamSize++;
151  if (employee->chr.HP > 0)
152  unitsAlive++;
153 
154  if (teamSize >= MAX_SOLDIERS_AUTOMISSION)
155  break;
156  }
157  battle->nUnits[teamNum] = teamSize;
158  battle->actUnits[teamNum] = unitsAlive;
159 
160  if (teamSize == 0) {
161  cgi->Com_DPrintf(DEBUG_CLIENT, "Warning: Attempt to add soldiers to an auto-mission from an aircraft with no soldiers onboard.\n");
162  cgi->Com_DPrintf(DEBUG_CLIENT, "--- Note: Aliens might win this mission by default because they are un-challenged, with no resistance!\n");
163  }
164  if (unitsAlive == 0) {
165  cgi->Com_DPrintf(DEBUG_CLIENT, "Warning: Attempt to add team to auto battle where all the units on the team are DEAD!\n");
166  cgi->Com_DPrintf(DEBUG_CLIENT, "--- Note: This team will LOSE the battle by default.\n");
167  }
168 
169  /* NOTE: For now these are hard-coded to values based upon general campaign difficulty.
170  * --- In the future, it might be easier to set this according to a scripted value in a .ufo
171  * --- file, with other campaign info. Reminder: Higher floating point values mean better
172  * --- soldiers, and therefore make an easier fight for the player. */
173  switch (campaign->difficulty) {
174  case 4:
175  battle->scoreTeamDifficulty[teamNum] = 0.30;
176  break;
177  case 3:
178  battle->scoreTeamDifficulty[teamNum] = 0.35;
179  break;
180  case 2:
181  battle->scoreTeamDifficulty[teamNum] = 0.40;
182  break;
183  case 1:
184  battle->scoreTeamDifficulty[teamNum] = 0.45;
185  break;
186  case 0:
187  battle->scoreTeamDifficulty[teamNum] = 0.50;
188  break;
189  case -1:
190  battle->scoreTeamDifficulty[teamNum] = 0.55;
191  break;
192  case -2:
193  battle->scoreTeamDifficulty[teamNum] = 0.60;
194  break;
195  case -3:
196  battle->scoreTeamDifficulty[teamNum] = 0.65;
197  break;
198  case -4:
199  battle->scoreTeamDifficulty[teamNum] = 0.70;
200  break;
201  default:
202  battle->scoreTeamDifficulty[teamNum] = 0.50;
203  }
204 }
205 
213 static void AM_CreateUnitChr (autoUnit_t* unit, const teamDef_t* teamDef, const equipDef_t* ed)
214 {
216  cgi->CL_GenerateCharacter(unit->chr, teamDef->id);
217 
218  cgi->INV_EquipActor(unit->chr, ed, unit->chr->teamDef->onlyWeapon, cgi->GAME_GetChrMaxLoad(unit->chr));
219 }
220 
226 static void AM_DestroyUnitChr (autoUnit_t* unit)
227 {
228  cgi->INV_DestroyInventory(&unit->chr->inv);
229  cgi->Free(unit->chr);
230 }
231 
237 static void AM_FillTeamFromBattleParams (autoMissionBattle_t* battle, const battleParam_t* missionParams)
238 {
239  assert(battle);
240  assert(missionParams);
241 
242  /* Aliens */
243  battle->nUnits[AUTOMISSION_TEAM_TYPE_ALIEN] = missionParams->aliens;
244  battle->actUnits[AUTOMISSION_TEAM_TYPE_ALIEN] = missionParams->aliens;
245  if (missionParams->aliens > 0) {
246  const equipDef_t* ed = cgi->INV_GetEquipmentDefinitionByID(missionParams->alienEquipment);
247  const alienTeamGroup_t* alienTeamGroup = missionParams->alienTeamGroup;
248  for (int unitIDX = 0; unitIDX < missionParams->aliens; unitIDX++) {
249  const teamDef_t* teamDef = alienTeamGroup->alienTeams[rand() % alienTeamGroup->numAlienTeams];
250  autoUnit_t* unit = AM_GetUnit(battle, AUTOMISSION_TEAM_TYPE_ALIEN, unitIDX);
251 
252  AM_CreateUnitChr(unit, teamDef, ed);
254  unit->idx = unitIDX;
255  }
256  battle->scoreTeamSkill[AUTOMISSION_TEAM_TYPE_ALIEN] = (frand() * 0.6f) + 0.2f;
257  }
258 
259  /* Civilians (if any) */
260  battle->nUnits[AUTOMISSION_TEAM_TYPE_CIVILIAN] = missionParams->civilians;
261  battle->actUnits[AUTOMISSION_TEAM_TYPE_CIVILIAN] = missionParams->civilians;
262  if (missionParams->civilians > 0) {
263  const teamDef_t* teamDef = cgi->Com_GetTeamDefinitionByID(missionParams->civTeam);
264  for (int unitIDX = 0; unitIDX < missionParams->civilians; unitIDX++) {
265  autoUnit_t* unit = AM_GetUnit(battle, AUTOMISSION_TEAM_TYPE_CIVILIAN, unitIDX);
266 
267  AM_CreateUnitChr(unit, teamDef, nullptr);
269  unit->idx = unitIDX;
270  }
271  battle->scoreTeamSkill[AUTOMISSION_TEAM_TYPE_CIVILIAN] = (frand() * 0.5f) + 0.05f;
272  }
273 }
274 
282 static void AM_SetDefaultHostilities (autoMissionBattle_t* battle, const bool civsInfected)
283 {
284  bool civsInverted = !civsInfected;
285 
287  int j;
289  if (battle->actUnits[team] <= 0)
290  continue;
291 
293  const autoMissionTeamType_t otherTeam = (autoMissionTeamType_t)j;
294  if (battle->actUnits[otherTeam] <= 0)
295  continue;
296 
297  if (AM_IsPlayer(team)) {
298  if (AM_IsAlien(otherTeam))
299  AM_SetHostile(battle, team, otherTeam, true);
300  else if (AM_IsPlayer(otherTeam))
301  AM_SetHostile(battle, team, otherTeam, false);
302  else if (AM_IsCivilian(otherTeam))
303  AM_SetHostile(battle, team, otherTeam, civsInfected);
304  } else if (AM_IsAlien(team)) {
305  if (AM_IsAlien(otherTeam))
306  AM_SetHostile(battle, team, otherTeam, false);
307  else if (AM_IsPlayer(otherTeam))
308  AM_SetHostile(battle, team, otherTeam, true);
309  else if (AM_IsCivilian(otherTeam))
310  AM_SetHostile(battle, team, otherTeam, civsInverted);
311  } else if (AM_IsCivilian(team)) {
312  if (AM_IsAlien(otherTeam))
313  AM_SetHostile(battle, team, otherTeam, civsInverted);
314  else if (AM_IsPlayer(otherTeam))
315  AM_SetHostile(battle, team, otherTeam, civsInfected);
316  else if (AM_IsCivilian(otherTeam))
317  AM_SetHostile(battle, team, otherTeam, false);
318  }
319  }
320  }
321 }
322 
328 {
329  int unitTotal = 0;
330  int isHostileTotal = 0;
331  int totalActiveTeams = 0;
332  int lastActiveTeam = -1;
333  int isHostileCount;
334  int team;
335  /* Sums of various values */
336  double teamPooledHealth[AUTOMISSION_TEAM_TYPE_MAX];
337  double teamPooledHealthMax[AUTOMISSION_TEAM_TYPE_MAX];
338  double teamPooledUnitsHealthy[AUTOMISSION_TEAM_TYPE_MAX];
339  double teamPooledUnitsTotal[AUTOMISSION_TEAM_TYPE_MAX];
340  /* Ratios */
341  double teamRatioHealthyUnits[AUTOMISSION_TEAM_TYPE_MAX];
342  double teamRatioHealthTotal[AUTOMISSION_TEAM_TYPE_MAX];
343 
344  for (team = 0; team < AUTOMISSION_TEAM_TYPE_MAX; team++) {
345  unitTotal += battle->nUnits[team];
346 
347  if (battle->actUnits[team] > 0) {
348  lastActiveTeam = team;
349  totalActiveTeams++;
350  }
351  for (isHostileCount = 0; isHostileCount < AUTOMISSION_TEAM_TYPE_MAX; isHostileCount++) {
352  if (battle->nUnits[isHostileCount] <= 0)
353  continue;
354 
355  if (battle->isHostile[team][isHostileCount] && battle->actUnits[team] > 0)
356  isHostileTotal++;
357  }
358  }
359 
360  /* sanity checks */
361  if (unitTotal == 0)
362  cgi->Com_Error(ERR_DROP, "Grand total of ZERO units are fighting in auto battle, something is wrong.");
363 
364  if (unitTotal < 0)
365  cgi->Com_Error(ERR_DROP, "Negative number of total units are fighting in auto battle, something is VERY wrong!");
366 
367  if (isHostileTotal <= 0)
368  cgi->Com_Error(ERR_DROP, "No team has any other team hostile toward it, no battle is possible!");
369 
370  if (totalActiveTeams <= 0)
371  cgi->Com_Error(ERR_DROP, "No Active teams detected in Auto Battle!");
372 
373  if (totalActiveTeams == 1) {
374  cgi->Com_DPrintf(DEBUG_CLIENT, "Note: Only one active team detected, this team will win the auto mission battle by default.\n");
375  battle->winningTeam = lastActiveTeam;
376  return;
377  }
378 
379  /* Set up teams */
380  for (team = 0; team < AUTOMISSION_TEAM_TYPE_MAX; team++) {
381  teamPooledHealth[team] = 0.0;
382  teamPooledHealthMax[team] = 0.0;
383  teamPooledUnitsHealthy[team] = 0.0;
384  teamPooledUnitsTotal[team] = 0.0;
385 
386  if (battle->actUnits[team] > 0) {
387  double skillAdjCalc;
388  double skillAdjCalcAbs;
389 
390  for (int currentUnit = 0; currentUnit < battle->nUnits[team]; currentUnit++) {
391  autoUnit_t* unit = AM_GetUnit(battle, team, currentUnit);
392  const character_t* chr = unit->chr;
393 
394  if (chr->HP <= 0)
395  continue;
396 
397  teamPooledHealth[team] += chr->HP;
398  teamPooledHealthMax[team] += chr->maxHP;
399  teamPooledUnitsTotal[team] += 1.0;
400  if (chr->HP == chr->maxHP)
401  teamPooledUnitsHealthy[team] += 1.0;
402  }
403  /* We shouldn't be dividing by zero here. */
404  assert(teamPooledHealthMax[team] > 0.0);
405  assert(teamPooledUnitsTotal[team] > 0.0);
406 
407  teamRatioHealthTotal[team] = teamPooledHealth[team] / teamPooledHealthMax[team];
408  teamRatioHealthyUnits[team] = teamPooledUnitsHealthy[team] / teamPooledUnitsTotal[team];
409 
410  /* In DEBUG mode, these should help with telling where things are at what time, for bug-hunting purposes. */
411  /* Note (Destructavator): Is there a better way to implement this? Is there a set protocol for this type of thing? */
412  cgi->Com_DPrintf(DEBUG_CLIENT, "Team %i has calculated ratio of healthy units of %f.\n",
413  team, teamRatioHealthyUnits[team]);
414  cgi->Com_DPrintf(DEBUG_CLIENT, "Team %i has calculated ratio of health values of %f.\n",
415  team, teamRatioHealthTotal[team]);
416 
418  skillAdjCalc = teamRatioHealthyUnits[team] + teamRatioHealthTotal[team];
419  skillAdjCalc *= 0.50;
420  skillAdjCalc = FpCurve1D_u_in(skillAdjCalc, 0.50, 0.50);
421  skillAdjCalc -= 0.50;
422  skillAdjCalcAbs = fabs(skillAdjCalc);
423  if (skillAdjCalc > 0.0)
424  battle->scoreTeamSkill[team] = ChkDNorm_Inv (FpCurveUp (battle->scoreTeamSkill[team], skillAdjCalcAbs) );
425  else if (skillAdjCalc < 0.0)
426  battle->scoreTeamSkill[team] = ChkDNorm (FpCurveDn (battle->scoreTeamSkill[team], skillAdjCalcAbs) );
427  /* if (skillAdjCalc == exact 0.0), no change to team's skill. */
428 
429  cgi->Com_DPrintf(DEBUG_CLIENT, "Team %i has adjusted skill rating of %f.\n",
430  team, battle->scoreTeamSkill[team]);
431  }
432  }
433 }
434 
441 static int AM_GetRandomTeam (autoMissionBattle_t* battle, int currTeam, bool enemy)
442 {
443  int eTeam;
444 
445  assert(battle);
446  assert(currTeam >= 0 && currTeam < AUTOMISSION_TEAM_TYPE_MAX);
447 
448  /* select a team randomly */
449  eTeam = rand () % AUTOMISSION_TEAM_TYPE_MAX;
450  /* if selected team is active and it's hostility match, we're ready */
451  if (battle->actUnits[eTeam] > 0 && AM_IsHostile(battle, currTeam, eTeam) == enemy) {
452  return eTeam;
453  } else {
454  int nextTeam;
455 
456  /* if not, check next */
457  for (nextTeam = (eTeam + 1) % AUTOMISSION_TEAM_TYPE_MAX; nextTeam != eTeam; nextTeam = (nextTeam + 1) % AUTOMISSION_TEAM_TYPE_MAX) {
458  if (battle->actUnits[nextTeam] > 0 && AM_IsHostile(battle, currTeam, nextTeam) == enemy)
459  return nextTeam;
460  }
461  /* none found */
463  }
464 }
465 
472 {
473  int idx;
474  autoUnit_t* unit;
475 
476  assert(battle);
477  if (team < 0 || team >= AUTOMISSION_TEAM_TYPE_MAX)
478  return nullptr;
479  if (battle->actUnits[team] <= 0)
480  return nullptr;
481  if (battle->nUnits[team] <= 0)
482  return nullptr;
483 
484  /* select a unit randomly */
485  idx = rand() % battle->nUnits[team];
486  unit = AM_GetUnit(battle, team, idx);
487 
488  /* if (s)he is active (alive, not stunned), we're ready */
489  if (AM_IsUnitActive(unit)) {
490  return unit;
491  } else {
492  int nextIdx;
493 
494  /* if not active, check next */
495  for (nextIdx = (idx + 1) % battle->nUnits[team]; nextIdx != idx; nextIdx = (nextIdx + 1) % battle->nUnits[team]) {
496  unit = AM_GetUnit(battle, team, nextIdx);
497  if (AM_IsUnitActive(unit))
498  return unit;
499  }
500  /* none found */
501  return nullptr;
502  }
503 }
504 
511 static autoUnit_t* AM_GetRandomActiveUnit (autoMissionBattle_t* battle, int currTeam, bool enemy)
512 {
513  int eTeam;
514  int nextTeam;
515  autoUnit_t* unit;
516 
517  assert(battle);
518  assert(currTeam >= 0 && currTeam < AUTOMISSION_TEAM_TYPE_MAX);
519 
520  eTeam = AM_GetRandomTeam(battle, currTeam, enemy);
521  if (eTeam >= AUTOMISSION_TEAM_TYPE_MAX)
522  return nullptr;
523 
524  unit = AM_GetRandomActiveUnitOfTeam(battle, eTeam);
525  if (unit)
526  return unit;
527 
528  /* if not, check next */
529  for (nextTeam = (eTeam + 1) % AUTOMISSION_TEAM_TYPE_MAX; nextTeam != eTeam; nextTeam = (nextTeam + 1) % AUTOMISSION_TEAM_TYPE_MAX) {
530  if (battle->actUnits[nextTeam] > 0 && AM_IsHostile(battle, currTeam, nextTeam) == enemy) {
531  unit = AM_GetRandomActiveUnitOfTeam(battle, nextTeam);
532  if (unit)
533  return unit;
534  }
535  }
536  /* none found */
537  return nullptr;
538 }
539 
547 static bool AM_CheckFire (autoMissionBattle_t* battle, autoUnit_t* currUnit, autoUnit_t* eUnit, const double effective)
548 {
549  character_t* currChr = currUnit->chr;
550  chrScoreGlobal_t* score = &currChr->score;
551  character_t* eChr = eUnit->chr;
552  double calcRand = frand();
553  int strikeDamage;
554 
555  if (AM_IsHostile(battle, currUnit->team, eUnit->team)) {
556  if (calcRand > effective)
557  return false;
558  strikeDamage = (int) (100.0 * battle->scoreTeamDifficulty[currUnit->team] * (effective - calcRand) / effective);
559  battle->teamAccomplishment[currUnit->team] += strikeDamage;
560  } else {
561  if (calcRand >= (0.050 - (effective * 0.050)))
562  return false;
563  strikeDamage = (int) (100.0 * (1.0 - battle->scoreTeamDifficulty[currUnit->team]) * calcRand);
564  battle->teamAccomplishment[currUnit->team] -= strikeDamage;
565  }
566 
567  eChr->HP = std::max(0, eChr->HP - strikeDamage);
568  /* Wound the target */
569  if (eChr->HP > 0)
570  eChr->wounds.treatmentLevel[eChr->teamDef->bodyTemplate->getRandomBodyPart()] += strikeDamage;
571 
572  /* If target is still active, continue */
573  if (AM_IsUnitActive(eUnit))
574  return true;
575 
576 #if DEBUG
577  cgi->Com_Printf("AutoBattle: Team: %d Unit: %d killed Team: %d Unit: %d\n", currUnit->team, currUnit->idx, eUnit->team, eUnit->idx);
578 #endif
579  battle->actUnits[eUnit->team]--;
580 
581  switch (currUnit->team) {
583  switch (eUnit->team) {
585  battle->results->ownSurvived--;
586  battle->results->ownKilledFriendlyFire++;
587  score->kills[KILLED_TEAM] += 1;
588  break;
590  battle->results->aliensSurvived--;
591  battle->results->aliensKilled++;
592  score->kills[KILLED_ENEMIES] += 1;
593  break;
595  battle->results->civiliansSurvived--;
597  score->kills[KILLED_CIVILIANS] += 1;
598  break;
599  default:
600  break;
601  }
602  break;
604  switch (eUnit->team) {
606  battle->results->ownSurvived--;
607  battle->results->ownKilled++;
608  break;
610  battle->results->aliensSurvived--;
611  battle->results->aliensKilled++;
612  break;
614  battle->results->civiliansSurvived--;
615  battle->results->civiliansKilled++;
616  break;
617  default:
618  break;
619  }
620  break;
622  switch (eUnit->team) {
624  battle->results->ownSurvived--;
625  battle->results->ownKilledFriendlyFire++;
626  break;
628  battle->results->aliensSurvived--;
629  battle->results->aliensKilled++;
630  break;
632  battle->results->civiliansSurvived--;
634  break;
635  default:
636  break;
637  }
638  break;
639  default:
640  break;
641  }
642  return true;
643 }
644 
651 static bool AM_UnitAttackEnemy (autoMissionBattle_t* battle, autoUnit_t* currUnit, const double effective)
652 {
653  autoUnit_t* eUnit;
654 
655  eUnit = AM_GetRandomActiveUnit(battle, currUnit->team, true);
656  /* no more enemies */
657  if (eUnit == nullptr)
658  return false;
659 
660  /* shot an enemy */
661  if (!AM_CheckFire(battle, currUnit, eUnit, effective)) {
662  /* if failed, attack a friendly */
663  eUnit = AM_GetRandomActiveUnit(battle, currUnit->team, false);
664  if (eUnit != nullptr)
665  AM_CheckFire(battle, currUnit, eUnit, effective);
666  }
667 
668  return true;
669 }
670 
675 static void AM_DoFight (autoMissionBattle_t* battle)
676 {
677  bool combatActive = true;
678 
679 #ifdef DEBUG
680  cgi->Com_Printf("Auto battle started\n");
681  for (int teamID = 0; teamID < AUTOMISSION_TEAM_TYPE_MAX; teamID++) {
682  cgi->Com_Printf("Team %d Units: %d\n", teamID, battle->nUnits[teamID]);
683  }
684 #endif
685 
686  while (combatActive) {
687  for (int team = 0; team < AUTOMISSION_TEAM_TYPE_MAX; team++) {
688  int aliveUnits;
689 
691  if (team == AUTOMISSION_TEAM_TYPE_CIVILIAN)
692  continue;
693 
694  if (battle->actUnits[team] <= 0)
695  continue;
696 
697  aliveUnits = 0;
698  /* Is this unit still alive (has any health left?) */
699  for (int currentUnit = 0; currentUnit < battle->nUnits[team]; currentUnit++) {
700  autoUnit_t* unit = AM_GetUnit(battle, team, currentUnit);
701  character_t* chr = unit->chr;
702  /* Wounded units don't fight quite as well */
703  const double hpLeftRatio = chr->HP / chr->maxHP;
704  const double effective = FpCurveDn(battle->scoreTeamSkill[team], hpLeftRatio * 0.50);
705 
706  if (!AM_IsUnitActive(unit))
707  continue;
708 
709  cgi->Com_DPrintf(DEBUG_CLIENT, "Unit %i on team %i has adjusted attack rating of %f.\n",
710  currentUnit, team, battle->scoreTeamSkill[team]);
711 
712  aliveUnits++;
713  combatActive = AM_UnitAttackEnemy(battle, unit, effective);
714  }
715  }
716  }
717 
718  /* Set results */
719  if (battle->actUnits[AUTOMISSION_TEAM_TYPE_PLAYER] <= 0) {
720  battle->results->state = LOST;
722  } else {
724  battle->results->state = WON;
725  }
726 }
727 
734 static void AM_DisplayResults (const autoMissionBattle_t* battle)
735 {
736  assert(battle);
737 
738  cgi->Cvar_SetValue("cp_mission_tryagain", 0);
739  if (battle->results->state == WON) {
740  cgi->UI_PushWindow("won");
742  MS_AddNewMessage(_("Notice"), _("You've won the battle"));
743  else
744  MS_AddNewMessage(_("Notice"), _("You've defeated the enemy, but did poorly, and many civilians were killed"));
745  } else {
746  cgi->UI_PushWindow("lost");
747  MS_AddNewMessage(_("Notice"), _("You've lost the battle"));
748  }
749 }
750 
757 {
758  assert(aircraft != nullptr);
759  assert(chr != nullptr);
760 
761  /* add items to itemcargo */
762  const Container* cont = nullptr;
763  while ((cont = chr->inv.getNextCont(cont))) {
764  Item* item = nullptr;
765  while ((item = cont->getNextItem(item))) {
766  if (item->def()) {
767  AII_CollectItem(aircraft, item->def(), 1);
768 
769  if (item->getAmmoLeft() && item->ammoDef())
770  AII_CollectItem(aircraft, item->ammoDef(), 1);
771  }
772  }
773  }
774 }
775 
781 static void AM_AlienCollect (aircraft_t* aircraft, const autoMissionBattle_t* battle)
782 {
783  assert(aircraft);
784  assert(battle);
785 
786  /* Aliens */
787  int collected = 0;
788  for (int unitIDX = 0; unitIDX < battle->nUnits[AUTOMISSION_TEAM_TYPE_ALIEN]; unitIDX++) {
789  const autoUnit_t* unit = AM_GetUnit(battle, AUTOMISSION_TEAM_TYPE_ALIEN, unitIDX);
790 
791  if (AM_IsUnitActive(unit))
792  continue;
793 
795  AL_AddAlienTypeToAircraftCargo(aircraft, unit->chr->teamDef, 1, unit->chr->HP <= 0);
796  collected++;
797  }
798 
799  if (collected > 0)
800  MS_AddNewMessage(_("Notice"), _("Collected alien bodies"));
801 }
802 
809 static void AM_UpdateSurivorsAfterBattle (const autoMissionBattle_t* battle, struct aircraft_s* aircraft)
810 {
811  assert(battle);
812  assert(battle->results);
813 
814  const int battleExperience = std::max(0, battle->teamAccomplishment[AUTOMISSION_TEAM_TYPE_PLAYER]);
815  int unit = 0;
816 
817  LIST_Foreach(aircraft->acTeam, Employee, soldier) {
818  if (unit >= MAX_SOLDIERS_AUTOMISSION)
819  break;
820 
821  unit++;
822 
823  character_t* chr = &soldier->chr;
824  /* dead soldiers are removed in CP_MissionEnd, just move their inventory to itemCargo */
825  if (chr->HP <= 0) {
826  if (battle->results->state == WON)
827  AM_MoveCharacterInventoryIntoItemCargo(aircraft, &soldier->chr);
829  continue;
830  }
831 
832  chrScoreGlobal_t* score = &chr->score;
833  for (int expCount = 0; expCount < ABILITY_NUM_TYPES; expCount++) {
834  const int maxXP = CHAR_GetMaxExperiencePerMission(static_cast<abilityskills_t>(expCount));
835  const int gainedXP = std::min(maxXP, static_cast<int>(battleExperience * ABILITY_AWARD_SCALE * frand()));
836  score->experience[expCount] += gainedXP;
837  cgi->Com_DPrintf(DEBUG_CLIENT, "AM_UpdateSurivorsAfterBattle: Soldier %s earned %d experience points in skill #%d (total experience: %d).\n",
838  chr->name, gainedXP, expCount, chr->score.experience[expCount]);
839  }
840 
841  for (int expCount = ABILITY_NUM_TYPES; expCount < SKILL_NUM_TYPES; expCount++) {
842  const int maxXP = CHAR_GetMaxExperiencePerMission(static_cast<abilityskills_t>(expCount));
843  const int gainedXP = std::min(maxXP, static_cast<int>(battleExperience * SKILL_AWARD_SCALE * frand()));
844  score->experience[expCount] += gainedXP;
845  cgi->Com_DPrintf(DEBUG_CLIENT, "AM_UpdateSurivorsAfterBattle: Soldier %s earned %d experience points in skill #%d (total experience: %d).\n",
846  chr->name, gainedXP, expCount, chr->score.experience[expCount]);
847  }
848  /* Health isn't part of abilityskills_t, so it needs to be handled separately. */
850  const int gainedXP = std::min(maxXP, static_cast<int>(battleExperience * ABILITY_AWARD_SCALE * frand()));
851  score->experience[SKILL_NUM_TYPES] += gainedXP;
852  cgi->Com_DPrintf(DEBUG_CLIENT, "AM_UpdateSurivorsAfterBattle: Soldier %s earned %d experience points in skill #%d (total experience: %d).\n",
853  chr->name, gainedXP, SKILL_NUM_TYPES, chr->score.experience[SKILL_NUM_TYPES]);
854 
855  CHAR_UpdateSkills(chr);
856  }
857 }
858 
864 {
865  int unitIDX;
866 
867  assert(battle);
868 
869  /* Aliens */
870  for (unitIDX = 0; unitIDX < battle->nUnits[AUTOMISSION_TEAM_TYPE_ALIEN]; unitIDX++) {
871  autoUnit_t* unit = AM_GetUnit(battle, AUTOMISSION_TEAM_TYPE_ALIEN, unitIDX);
872 
873  AM_DestroyUnitChr(unit);
874  }
875 
876  /* Civilians */
877  for (unitIDX = 0; unitIDX < battle->nUnits[AUTOMISSION_TEAM_TYPE_CIVILIAN]; unitIDX++) {
878  autoUnit_t* unit = AM_GetUnit(battle, AUTOMISSION_TEAM_TYPE_CIVILIAN, unitIDX);
879 
880  AM_DestroyUnitChr(unit);
881  }
882 }
883 
892 void AM_Go (mission_t* mission, aircraft_t* aircraft, const campaign_t* campaign, const battleParam_t* battleParameters, missionResults_t* results)
893 {
894  autoMissionBattle_t autoBattle;
895 
896  assert(mission);
897  assert(aircraft);
898  assert(aircraft->homebase);
899 
900  if (mission && mission->mapDef && mission->mapDef->storyRelated) {
901  cgi->Com_Printf("Story-related mission cannot be done via automission\n");
902  return;
903  }
904 
905  AM_ClearBattle(&autoBattle);
906  autoBattle.results = results;
907  AM_FillTeamFromAircraft(&autoBattle, AUTOMISSION_TEAM_TYPE_PLAYER, aircraft, campaign);
908  AM_FillTeamFromBattleParams(&autoBattle, battleParameters);
909  AM_SetDefaultHostilities(&autoBattle, false);
910  AM_CalculateTeamScores(&autoBattle);
911 
912  OBJZERO(*results);
913  results->ownSurvived = autoBattle.nUnits[AUTOMISSION_TEAM_TYPE_PLAYER];
914  results->aliensSurvived = autoBattle.nUnits[AUTOMISSION_TEAM_TYPE_ALIEN];
916  results->mission = mission;
917 
918  ccs.eMission = aircraft->homebase->storage; /* copied, including arrays inside! */
919 
920  AM_DoFight(&autoBattle);
921 
922  AM_UpdateSurivorsAfterBattle(&autoBattle, aircraft);
923  if (results->state == WON)
924  AM_AlienCollect(aircraft, &autoBattle);
925 
926  MIS_InitResultScreen(results);
928  ccs.missionResultCallback(results);
929  }
930 
931  AM_DisplayResults(&autoBattle);
932  AM_CleanBattleParameters(&autoBattle);
933 }
934 
938 void AM_InitStartup (void)
939 {
940 }
941 
945 void AM_Shutdown (void)
946 {
947 }
static bool AM_UnitAttackEnemy(autoMissionBattle_t *battle, autoUnit_t *currUnit, const double effective)
Make Unit attack his enemies (or friends)
const teamDef_t * alienTeams[MAX_TEAMS_PER_MISSION]
Definition: cp_campaign.h:114
struct base_s * homebase
Definition: cp_aircraft.h:150
Header file for single player automatic (quick, simulated) missions, without going to the battlescape...
int CHAR_GetMaxExperiencePerMission(const abilityskills_t skill)
Determines the maximum amount of XP per skill that can be gained from any one mission.
static void AM_CreateUnitChr(autoUnit_t *unit, const teamDef_t *teamDef, const equipDef_t *ed)
Create character for a Unit.
#define ABILITY_AWARD_SCALE
mission definition
Definition: cp_missions.h:86
Header file for character (soldier, alien) related campaign functions.
Item * getNextItem(const Item *prev) const
Definition: inv_shared.cpp:671
alienTeamGroup_t * alienTeamGroup
Definition: cp_campaign.h:141
int experience[SKILL_NUM_TYPES+1]
Definition: chr_shared.h:120
void AII_CollectItem(aircraft_t *aircraft, const objDef_t *item, int amount)
Add an item to aircraft inventory.
header file UI callbacks for missions.
static void AM_FillTeamFromBattleParams(autoMissionBattle_t *battle, const battleParam_t *missionParams)
Creates team data for alien and civilian teams based on the mission parameters data.
#define ChkDNorm_Inv(fpVal)
This takes a floating-point variable and, if it happens to have a value of perfect 1...
Definition: mathlib_extra.h:78
#define _(String)
Definition: cl_shared.h:44
#define SKILL_AWARD_SCALE
Constants for automission experience gain factors.
double scoreTeamDifficulty[AUTOMISSION_TEAM_TYPE_MAX]
const BodyData * bodyTemplate
Definition: chr_shared.h:350
const objDef_t * ammoDef(void) const
Definition: inv_shared.h:460
static void AM_SetDefaultHostilities(autoMissionBattle_t *battle, const bool civsInfected)
Run this on an auto mission battle before the battle is actually simulated, to set default values for...
double defendStrength
int teamAccomplishment[AUTOMISSION_TEAM_TYPE_MAX]
char civTeam[MAX_VAR]
Definition: cp_campaign.h:145
Describes a character with all its attributes.
Definition: chr_shared.h:388
equipDef_t eMission
Definition: cp_campaign.h:230
autoMissionTeamType_t
Possible types of teams that can fight in an auto mission battle.
int ownKilledFriendlyFire
Definition: cp_missions.h:75
short getRandomBodyPart(void) const
Definition: chr_shared.cpp:341
static void AM_MoveCharacterInventoryIntoItemCargo(aircraft_t *aircraft, character_t *chr)
Move equipment carried by the soldier/alien to the aircraft&#39;s itemcargo bay.
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
#define ABILITY_NUM_TYPES
Definition: chr_shared.h:54
#define AM_IsHostile(battle, team, otherTeam)
static void AM_CalculateTeamScores(autoMissionBattle_t *battle)
Calcuates Team strength scores for autobattle.
const teamDef_t * teamDef
Definition: chr_shared.h:413
uiMessageListNodeMessage_t * MS_AddNewMessage(const char *title, const char *text, messageType_t type, technology_t *pedia, bool popup, bool playSound)
Adds a new message to message stack.
Definition: cp_messages.cpp:63
#define AM_IsPlayer(type)
memPool_t * cp_campaignPool
Definition: cp_campaign.cpp:62
const struct mission_s * mission
Definition: cp_missions.h:62
missionResults_t * results
static void AM_UpdateSurivorsAfterBattle(const autoMissionBattle_t *battle, struct aircraft_s *aircraft)
This looks at a finished auto battle, and uses values from it to kill or lower health of surviving so...
character_t * chr
missionState_t state
Definition: cp_missions.h:63
item instance data, with linked list capability
Definition: inv_shared.h:402
char id[MAX_VAR]
Definition: chr_shared.h:309
static autoUnit_t * AM_GetRandomActiveUnit(autoMissionBattle_t *battle, int currTeam, bool enemy)
returns a randomly selected active unit
Campaign mission triggers.
#define ERR_DROP
Definition: common.h:211
#define DEBUG_CLIENT
Definition: defines.h:59
bool AL_AddAlienTypeToAircraftCargo(aircraft_t *aircraft, const teamDef_t *teamDef, int amount, bool dead)
Adds an alientype to an aircraft cargo.
Structure of all stats collected for an actor over time.
Definition: chr_shared.h:119
#define OBJZERO(obj)
Definition: shared.h:178
#define AM_GetUnit(battle, teamIdx, unitIdx)
static int AM_GetRandomTeam(autoMissionBattle_t *battle, int currTeam, bool enemy)
returns a randomly selected active team
bool isHostile[AUTOMISSION_TEAM_TYPE_MAX][AUTOMISSION_TEAM_TYPE_MAX]
Campaign missions headers.
void CHAR_UpdateSkills(character_t *chr)
Updates the character skills after a mission.
const cgame_import_t * cgi
int treatmentLevel[BODYPART_MAXTYPE]
Definition: chr_shared.h:363
static void AM_DestroyUnitChr(autoUnit_t *unit)
Destroys character of a Unit.
static autoUnit_t * AM_GetRandomActiveUnitOfTeam(autoMissionBattle_t *battle, int team)
returns a randomly selected alive unit from a team
double FpCurve1D_u_in(double fpVal, double mEffect, double cntPnt)
Takes a floating-point value (double) between 0.0 and 1.0 and returns a new value within the same ran...
static void AM_DoFight(autoMissionBattle_t *battle)
Main Battle loop function.
ccs_t ccs
Definition: cp_campaign.cpp:63
#define AM_IsCivilian(type)
woundInfo_t wounds
Definition: chr_shared.h:402
const equipDef_t *IMPORT * INV_GetEquipmentDefinitionByID(const char *name)
autoMissionTeamType_t team
const teamDef_t *IMPORT * Com_GetTeamDefinitionByID(const char *team)
linkedList_t * acTeam
Definition: cp_aircraft.h:140
double FpCurveUp(double fpVal, double mEffect)
Takes a floating-point value (double) between 0.0 and 1.0 and returns a new value within the same ran...
Header for Geoscape management.
void AM_Go(mission_t *mission, aircraft_t *aircraft, const campaign_t *campaign, const battleParam_t *battleParameters, missionResults_t *results)
Handles the auto mission.
double scoreTeamEquipment[AUTOMISSION_TEAM_TYPE_MAX]
Data structure for a simulated or auto mission.
missionResultFunction_t missionResultCallback
Definition: cp_campaign.h:388
void E_RemoveInventoryFromStorage(Employee *employee)
Removes the items of an employee (soldier) from the base storage (s)he is hired at.
QGL_EXTERN GLfloat f
Definition: r_gl.h:114
#define ChkDNorm(fpVal)
This takes a floating-point variable and, if it happens to have a value of perfect 0...
Definition: mathlib_extra.h:67
An aircraft with all it&#39;s data.
Definition: cp_aircraft.h:115
double attackStrength
#define AM_IsAlien(type)
int kills[KILLED_NUM_TYPES]
Definition: chr_shared.h:126
float frand(void)
Return random values between 0 and 1.
Definition: mathlib.cpp:506
QGL_EXTERN GLint i
Definition: r_gl.h:113
static void AM_ClearBattle(autoMissionBattle_t *battle)
Clears, initializes, or resets a single auto mission, sets default values.
Structure with mission info needed to create results summary at menu won.
Definition: cp_missions.h:61
int civiliansKilledFriendlyFire
Definition: cp_missions.h:78
chrScoreGlobal_t score
Definition: chr_shared.h:406
static void AM_CleanBattleParameters(autoMissionBattle_t *battle)
Clean up alien and civilian teams.
static void AM_AlienCollect(aircraft_t *aircraft, const autoMissionBattle_t *battle)
Collect alien bodies and items after battle.
void AM_Shutdown(void)
Closing actions for automission-subsystem.
alien team group definition.
Definition: cp_campaign.h:106
#define LIST_Foreach(list, type, var)
Iterates over a linked list, it&#39;s safe to delete the returned entry from the list while looping over ...
Definition: list.h:41
const objDef_t * onlyWeapon
Definition: chr_shared.h:336
static void AM_FillTeamFromAircraft(autoMissionBattle_t *battle, const autoMissionTeamType_t teamNum, const aircraft_t *aircraft, const campaign_t *campaign)
Adds team data for a specified team in an auto-mission object, from a (player) aircraft.
Header file for single player campaign control.
char alienEquipment[MAX_VAR]
Definition: cp_campaign.h:144
#define MAX_SOLDIERS_AUTOMISSION
signed int difficulty
Definition: cp_campaign.h:186
short actUnits[AUTOMISSION_TEAM_TYPE_MAX]
char name[MAX_VAR]
Definition: chr_shared.h:390
One unit (soldier/alien/civilian) of the autobattle.
static void AM_DisplayResults(const autoMissionBattle_t *battle)
This will display on-screen, for the player, results of the auto mission.
short nUnits[AUTOMISSION_TEAM_TYPE_MAX]
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
Definition: inv_shared.cpp:722
#define Mem_PoolAllocType(type, pool)
Definition: mem.h:43
const objDef_t * def(void) const
Definition: inv_shared.h:469
bool storyRelated
Definition: q_shared.h:489
#define AM_IsUnitActive(unit)
static bool AM_CheckFire(autoMissionBattle_t *battle, autoUnit_t *currUnit, autoUnit_t *eUnit, const double effective)
Check and do attack on a team.
int getAmmoLeft() const
Definition: inv_shared.h:466
#define AM_SetHostile(battle, team, otherTeam, value)
mapDef_t * mapDef
Definition: cp_missions.h:89
void AM_InitStartup(void)
Init actions for automission-subsystem.
double scoreTeamSkill[AUTOMISSION_TEAM_TYPE_MAX]
Inventory inv
Definition: chr_shared.h:411
double FpCurveDn(double fpVal, double mEffect)
Takes a floating-point value (double) between 0.0 and 1.0 and returns a new value within the same ran...
void MIS_InitResultScreen(const missionResults_t *results)
Updates mission result menu text with appropriate values.