UFO: Alien Invasion
Loading...
Searching...
No Matches
cp_auto_mission.cpp
Go to the documentation of this file.
1
5
6/*
7Copyright (C) 2002-2025 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, 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"
34#include "math.h"
36
47
48#define MAX_SOLDIERS_AUTOMISSION MAX_TEAMS * AUTOMISSION_TEAM_TYPE_MAX
49
61
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
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
132static 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
213static 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
226static void AM_DestroyUnitChr (autoUnit_t* unit)
227{
228 cgi->INV_DestroyInventory(&unit->chr->inv);
229 cgi->Free(unit->chr);
230}
231
237static 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
282static 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
441static 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
511static 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
547static 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--;
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--;
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
651static 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
675static 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
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
734static 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
781static 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
809static 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
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
892void 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];
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);
927 if (ccs.missionResultCallback) {
928 ccs.missionResultCallback(results);
929 }
930
931 AM_DisplayResults(&autoBattle);
932 AM_CleanBattleParameters(&autoBattle);
933}
934
938void AM_InitStartup (void)
939{
940}
941
945void AM_Shutdown (void)
946{
947}
abilityskills_t
Definition chr_shared.h:36
@ SKILL_NUM_TYPES
Definition chr_shared.h:51
@ KILLED_TEAM
Definition chr_shared.h:30
@ KILLED_ENEMIES
Definition chr_shared.h:28
@ KILLED_CIVILIANS
Definition chr_shared.h:29
#define ABILITY_NUM_TYPES
Definition chr_shared.h:54
Header file for inventory handling and Equipment menu.
Share stuff between the different cgame implementations.
#define _(String)
Definition cl_shared.h:44
short getRandomBodyPart(void) const
Item * getNextItem(const Item *prev) const
const Container * getNextCont(const Container *prev, bool inclTemp=false) const
item instance data, with linked list capability
Definition inv_shared.h:402
const objDef_t * ammoDef(void) const
Definition inv_shared.h:460
int getAmmoLeft() const
Definition inv_shared.h:466
const objDef_t * def(void) const
Definition inv_shared.h:469
#define ERR_DROP
Definition common.h:211
void AII_CollectItem(aircraft_t *aircraft, const objDef_t *item, int amount)
Add an item to aircraft inventory.
bool AL_AddAlienTypeToAircraftCargo(aircraft_t *aircraft, const teamDef_t *teamDef, int amount, bool dead)
Adds an alientype to an aircraft cargo.
static void AM_CreateUnitChr(autoUnit_t *unit, const teamDef_t *teamDef, const equipDef_t *ed)
Create character for a Unit.
static void AM_DoFight(autoMissionBattle_t *battle)
Main Battle loop function.
#define ABILITY_AWARD_SCALE
void AM_Go(mission_t *mission, aircraft_t *aircraft, const campaign_t *campaign, const battleParam_t *battleParameters, missionResults_t *results)
Handles the auto mission.
static bool AM_UnitAttackEnemy(autoMissionBattle_t *battle, autoUnit_t *currUnit, const double effective)
Make Unit attack his enemies (or friends).
#define MAX_SOLDIERS_AUTOMISSION
static int AM_GetRandomTeam(autoMissionBattle_t *battle, int currTeam, bool enemy)
returns a randomly selected active team
#define AM_IsUnitActive(unit)
#define AM_IsPlayer(type)
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...
#define SKILL_AWARD_SCALE
Constants for automission experience gain factors.
#define AM_IsAlien(type)
void AM_InitStartup(void)
Init actions for automission-subsystem.
static void AM_MoveCharacterInventoryIntoItemCargo(aircraft_t *aircraft, character_t *chr)
Move equipment carried by the soldier/alien to the aircraft's itemcargo bay.
#define AM_IsHostile(battle, team, otherTeam)
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.
static autoUnit_t * AM_GetRandomActiveUnit(autoMissionBattle_t *battle, int currTeam, bool enemy)
returns a randomly selected active unit
#define AM_SetHostile(battle, team, otherTeam, value)
static void AM_AlienCollect(aircraft_t *aircraft, const autoMissionBattle_t *battle)
Collect alien bodies and items after battle.
static void AM_DisplayResults(const autoMissionBattle_t *battle)
This will display on-screen, for the player, results of the auto mission.
autoMissionTeamType_t
Possible types of teams that can fight in an auto mission battle.
@ AUTOMISSION_TEAM_TYPE_MAX
@ AUTOMISSION_TEAM_TYPE_ALIEN
@ AUTOMISSION_TEAM_TYPE_CIVILIAN
@ AUTOMISSION_TEAM_TYPE_PLAYER
void AM_Shutdown(void)
Closing actions for automission-subsystem.
static autoUnit_t * AM_GetRandomActiveUnitOfTeam(autoMissionBattle_t *battle, int team)
returns a randomly selected alive unit from a team
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.
static void AM_CleanBattleParameters(autoMissionBattle_t *battle)
Clean up alien and civilian teams.
static void AM_CalculateTeamScores(autoMissionBattle_t *battle)
Calcuates Team strength scores for autobattle.
static void AM_DestroyUnitChr(autoUnit_t *unit)
Destroys character of a Unit.
#define AM_IsCivilian(type)
#define AM_GetUnit(battle, teamIdx, unitIdx)
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...
static bool AM_CheckFire(autoMissionBattle_t *battle, autoUnit_t *currUnit, autoUnit_t *eUnit, const double effective)
Check and do attack on a team.
static void AM_ClearBattle(autoMissionBattle_t *battle)
Clears, initializes, or resets a single auto mission, sets default values.
Header file for single player automatic (quick, simulated) missions, without going to the battlescape...
memPool_t * cp_campaignPool
ccs_t ccs
Header file for single player campaign control.
const cgame_import_t * cgi
int CHAR_GetMaxExperiencePerMission(const abilityskills_t skill)
Determines the maximum amount of XP per skill that can be gained from any one mission.
void CHAR_UpdateSkills(character_t *chr)
Updates the character skills after a mission.
Header file for character (soldier, alien) related campaign functions.
void E_RemoveInventoryFromStorage(Employee *employee)
Removes the items of an employee (soldier) from the base storage (s)he is hired at.
Header for Geoscape management.
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.
void MIS_InitResultScreen(const missionResults_t *results)
Updates mission result menu text with appropriate values.
header file UI callbacks for missions.
Campaign mission triggers.
Campaign missions headers.
@ LOST
Definition cp_missions.h:57
@ WON
Definition cp_missions.h:57
#define DEBUG_CLIENT
Definition defines.h:59
typedef int(ZCALLBACK *close_file_func) OF((voidpf opaque
#define LIST_Foreach(list, type, var)
Iterates over a linked list, it's safe to delete the returned entry from the list while looping over ...
Definition list.h:41
float frand(void)
Return random values between 0 and 1.
Definition mathlib.cpp:506
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...
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...
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...
Special, additional math algorithms for floating-point values.
#define ChkDNorm(fpVal)
This takes a floating-point variable and, if it happens to have a value of perfect 0....
#define ChkDNorm_Inv(fpVal)
This takes a floating-point variable and, if it happens to have a value of perfect 1....
#define Mem_PoolAllocType(type, pool)
Definition mem.h:43
QGL_EXTERN GLint i
Definition r_gl.h:113
#define OBJZERO(obj)
Definition shared.h:178
An aircraft with all it's data.
struct base_s * homebase
linkedList_t * acTeam
alien team group definition.
const teamDef_t * alienTeams[MAX_TEAMS_PER_MISSION]
Data structure for a simulated or auto mission.
short nUnits[AUTOMISSION_TEAM_TYPE_MAX]
short actUnits[AUTOMISSION_TEAM_TYPE_MAX]
autoUnit_t units[AUTOMISSION_TEAM_TYPE_MAX][MAX_SOLDIERS_AUTOMISSION]
bool isHostile[AUTOMISSION_TEAM_TYPE_MAX][AUTOMISSION_TEAM_TYPE_MAX]
int teamAccomplishment[AUTOMISSION_TEAM_TYPE_MAX]
double scoreTeamEquipment[AUTOMISSION_TEAM_TYPE_MAX]
missionResults_t * results
double scoreTeamDifficulty[AUTOMISSION_TEAM_TYPE_MAX]
double scoreTeamSkill[AUTOMISSION_TEAM_TYPE_MAX]
One unit (soldier/alien/civilian) of the autobattle.
character_t * chr
double attackStrength
double defendStrength
autoMissionTeamType_t team
alienTeamGroup_t * alienTeamGroup
char alienEquipment[MAX_VAR]
char civTeam[MAX_VAR]
signed int difficulty
Describes a character with all its attributes.
Definition chr_shared.h:388
const teamDef_t * teamDef
Definition chr_shared.h:413
chrScoreGlobal_t score
Definition chr_shared.h:406
woundInfo_t wounds
Definition chr_shared.h:402
char name[MAX_VAR]
Definition chr_shared.h:390
Inventory inv
Definition chr_shared.h:411
Structure of all stats collected for an actor over time.
Definition chr_shared.h:119
int kills[KILLED_NUM_TYPES]
Definition chr_shared.h:126
int experience[SKILL_NUM_TYPES+1]
Definition chr_shared.h:120
bool storyRelated
Definition q_shared.h:489
mission definition
Definition cp_missions.h:86
mapDef_t * mapDef
Definition cp_missions.h:89
Structure with mission info needed to create results summary at menu won.
Definition cp_missions.h:61
int civiliansKilledFriendlyFire
Definition cp_missions.h:78
const struct mission_s * mission
Definition cp_missions.h:62
missionState_t state
Definition cp_missions.h:63
const objDef_t * onlyWeapon
Definition chr_shared.h:336
char id[MAX_VAR]
Definition chr_shared.h:309
const BodyData * bodyTemplate
Definition chr_shared.h:350
int treatmentLevel[BODYPART_MAXTYPE]
Definition chr_shared.h:363