1 /**
2 * @file
3 * @brief Single player employee stuff.
4 * @note Employee related functions prefix: E_
5 */
6
7 /*
8 Copyright (C) 2002-2013 UFO: Alien Invasion.
9
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
18
19 See the GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 */
25
26 #include "../../cl_shared.h"
27 #include "../cl_game.h" /* GAME_GetTeamDef */
28 #include "../cl_game_team.h" /* character xml loading */
29 #include "cp_campaign.h"
30 #include "cp_employee_callbacks.h"
31 #include "cp_rank.h"
32 #include "cp_popup.h"
33 #include "save/save_employee.h"
34
35 /**
36 * @brief Returns number of employees of a type
37 * @param[in] type Employeetype to check
38 */
E_CountByType(employeeType_t type)39 int E_CountByType (employeeType_t type)
40 {
41 return cgi->LIST_Count(ccs.employees[type]);
42 }
43
44 /**
45 * @brief Iterates through unhired employees
46 * @param[in] type Employee type to look for
47 * @sa employeeType_t
48 */
E_GetUnhired(employeeType_t type)49 Employee* E_GetUnhired (employeeType_t type)
50 {
51 E_Foreach(type, employee) {
52 if (!employee->isHired())
53 return employee;
54 }
55
56 return nullptr;
57 }
58
59 /**
60 * @brief Tells you if a employee is away from his home base (gone in mission).
61 * @return bool true if the employee is away in mission, false if he is not or he is unhired.
62 */
isAwayFromBase() const63 bool Employee::isAwayFromBase () const
64 {
65 /* Check that employee is hired */
66 if (!isHired())
67 return false;
68
69 /* Check if employee is currently transferred. */
70 if (transfer)
71 return true;
72
73 /* for now only soldiers, ugvs and pilots can be assigned to an aircraft */
74 if (!isSoldier() && !isRobot() && !isPilot())
75 return false;
76
77 /* Crashed aircraft no longer belongs to any base but poor pilot/soldiers assigned
78 * to it are definitely away from the base so we need to iterate through all aircraft */
79 AIR_Foreach(aircraft) {
80 if (aircraft->homebase != baseHired)
81 continue;
82 if (!AIR_IsAircraftInBase(aircraft) && AIR_IsEmployeeInAircraft(this, aircraft))
83 return true;
84 }
85 return false;
86 }
87
88 /**
89 * @brief Hires some employees of appropriate type for a building
90 * @param[in] base Which base the employee should be hired in.
91 * @param[in] building in which building
92 * @param[in] num how many employees, if -1, hire building->maxEmployees
93 * @sa B_SetUpBase
94 */
E_HireForBuilding(base_t * base,building_t * building,int num)95 void E_HireForBuilding (base_t* base, building_t* building, int num)
96 {
97 if (num < 0)
98 num = building->maxEmployees;
99
100 if (num) {
101 employeeType_t employeeType;
102 switch (building->buildingType) {
103 case B_WORKSHOP:
104 employeeType = EMPL_WORKER;
105 break;
106 case B_LAB:
107 employeeType = EMPL_SCIENTIST;
108 break;
109 case B_HANGAR: /* the Dropship Hangar */
110 employeeType = EMPL_SOLDIER;
111 break;
112 case B_MISC:
113 Com_DPrintf(DEBUG_CLIENT, "E_HireForBuilding: Misc building type: %i with employees: %i.\n", building->buildingType, num);
114 return;
115 default:
116 Com_DPrintf(DEBUG_CLIENT, "E_HireForBuilding: Unknown building type: %i.\n", building->buildingType);
117 return;
118 }
119 /* don't try to hire more that available - see E_CreateEmployee */
120 num = std::min(num, E_CountByType(employeeType));
121 for (;num--;) {
122 assert(base);
123 if (!E_HireEmployeeByType(base, employeeType)) {
124 Com_DPrintf(DEBUG_CLIENT, "E_HireForBuilding: Hiring %i employee(s) of type %i failed.\n", num, employeeType);
125 return;
126 }
127 }
128 }
129 }
130
131 /**
132 * Will change the base where the employee is located in and will also update the
133 * capacity in the affected bases.
134 * @note Doesn't make any capacity checks and the employee must be hired already.
135 * @param employee The employee to change the base for
136 * @param newBase The base where the employee should be located at
137 * @return @c false if @c employee was a @c nullptr pointer
138 */
E_MoveIntoNewBase(Employee * employee,base_t * newBase)139 bool E_MoveIntoNewBase (Employee* employee, base_t* newBase)
140 {
141 if (employee) {
142 base_t* oldBase = employee->baseHired;
143 assert(oldBase);
144 employee->baseHired = newBase;
145 /* Remove employee from corresponding capacity */
146 switch (employee->getType()) {
147 case EMPL_PILOT:
148 case EMPL_WORKER:
149 case EMPL_SCIENTIST:
150 case EMPL_SOLDIER:
151 CAP_AddCurrent(oldBase, CAP_EMPLOYEES, -1);
152 CAP_AddCurrent(newBase, CAP_EMPLOYEES, 1);
153 break;
154 case EMPL_ROBOT:
155 CAP_AddCurrent(oldBase, CAP_ITEMS, -UGV_SIZE);
156 CAP_AddCurrent(newBase, CAP_ITEMS, UGV_SIZE);
157 break;
158 case MAX_EMPL:
159 break;
160 }
161 return true;
162 }
163
164 return false;
165 }
166
167 /**
168 * @brief Convert employeeType_t to translated string
169 * @param type employeeType_t value
170 * @param n number of persons of that kind (for plural detection)
171 * @return translated employee string
172 */
E_GetEmployeeString(employeeType_t type,int n)173 const char* E_GetEmployeeString (employeeType_t type, int n)
174 {
175 switch (type) {
176 case EMPL_SOLDIER:
177 return ngettext("Soldier", "Soldiers", n);
178 case EMPL_SCIENTIST:
179 return ngettext("Scientist", "Scientists", n);
180 case EMPL_WORKER:
181 return ngettext("Worker", "Workers", n);
182 case EMPL_PILOT:
183 return ngettext("Pilot", "Pilots", n);
184 case EMPL_ROBOT:
185 return ngettext("UGV", "UGVs", n);
186 default:
187 cgi->Com_Error(ERR_DROP, "Unknown employee type '%i'\n", type);
188 }
189 }
190
191 /**
192 * @brief Convert string to employeeType_t
193 * @param type Pointer to employee type string
194 * @return employeeType_t
195 * @todo use cgi->Com_ConstInt*
196 */
E_GetEmployeeType(const char * type)197 employeeType_t E_GetEmployeeType (const char* type)
198 {
199 if (!type)
200 return MAX_EMPL;
201
202 /* employee types as string */
203 if (Q_streq(type, "EMPL_SCIENTIST"))
204 return EMPL_SCIENTIST;
205 else if (Q_streq(type, "EMPL_SOLDIER"))
206 return EMPL_SOLDIER;
207 else if (Q_streq(type, "EMPL_WORKER"))
208 return EMPL_WORKER;
209 else if (Q_streq(type, "EMPL_PILOT"))
210 return EMPL_PILOT;
211 else if (Q_streq(type, "EMPL_ROBOT"))
212 return EMPL_ROBOT;
213
214 /* human readable employee type strings */
215 if (Q_streq(type, "scientist"))
216 return EMPL_SCIENTIST;
217 else if (Q_streq(type, "soldier"))
218 return EMPL_SOLDIER;
219 else if (Q_streq(type, "worker"))
220 return EMPL_WORKER;
221 else if (Q_streq(type, "pilot"))
222 return EMPL_PILOT;
223 else if (Q_streq(type, "robot"))
224 return EMPL_ROBOT;
225 else if (Q_streq(type, "ugv"))
226 return EMPL_ROBOT;
227
228 return MAX_EMPL;
229 }
230
231 /**
232 * @brief Return a "not hired" ugv-employee pointer of a given ugv-type.
233 * @param[in] ugvType What type of robot we want.
234 * @return Employee pointer on success or nullptr on error.
235 * @sa E_GetHiredRobot
236 */
E_GetUnhiredRobot(const ugv_t * ugvType)237 Employee* E_GetUnhiredRobot (const ugv_t* ugvType)
238 {
239 E_Foreach(EMPL_ROBOT, employee) {
240 if (!employee->isHired()) {
241 /* If no type was given we return the first ugv we find. */
242 if (!ugvType || employee->getUGV() == ugvType)
243 return employee;
244 }
245 }
246
247 return nullptr;
248 }
249
250 /**
251 * @brief Return a list of hired employees in the given base of a given type
252 * @param[in] base Which base the employee should be searched in. If nullptr is given employees in all bases will be listed.
253 * @param[in] type Which employee type to search for.
254 * @param[out] hiredEmployees Linked list of hired employees in the base.
255 * @return Number of hired employees in the base that are currently not on a transfer. Or @c -1 in case of an error.
256 */
E_GetHiredEmployees(const base_t * const base,employeeType_t type,linkedList_t ** hiredEmployees)257 int E_GetHiredEmployees (const base_t* const base, employeeType_t type, linkedList_t** hiredEmployees)
258 {
259 if (type >= MAX_EMPL) {
260 Com_Printf("E_GetHiredEmployees: Unknown EmployeeType: %i\n", type);
261 *hiredEmployees = nullptr;
262 return -1;
263 }
264
265 cgi->LIST_Delete(hiredEmployees);
266
267 E_Foreach(type, employee) {
268 if (!employee->isHired())
269 continue;
270 if (!employee->transfer && (!base || employee->isHiredInBase(base))) {
271 cgi->LIST_AddPointer(hiredEmployees, employee);
272 }
273 }
274
275 return cgi->LIST_Count(*hiredEmployees);
276 }
277
278 /**
279 * @brief Return a "hired" ugv-employee pointer of a given ugv-type in a given base.
280 * @param[in] base Which base the ugv should be searched in.c
281 * @param[in] ugvType What type of robot we want.
282 * @return Employee pointer on success or nullptr on error.
283 * @sa E_GetUnhiredRobot
284 */
E_GetHiredRobot(const base_t * const base,const ugv_t * ugvType)285 Employee* E_GetHiredRobot (const base_t* const base, const ugv_t* ugvType)
286 {
287 linkedList_t* hiredEmployees = nullptr;
288 Employee* employee;
289
290 E_GetHiredEmployees(base, EMPL_ROBOT, &hiredEmployees);
291
292 employee = nullptr;
293 LIST_Foreach(hiredEmployees, Employee, e) {
294 if ((e->getUGV() == ugvType || !ugvType) /* If no type was given we return the first ugv we find. */
295 && e->isHiredInBase(base)) { /* It has to be in the defined base. */
296 assert(e->isHired());
297 employee = e;
298 break;
299 }
300 }
301
302 cgi->LIST_Delete(&hiredEmployees);
303
304 if (!employee)
305 Com_DPrintf(DEBUG_CLIENT, "Could not get unhired ugv/robot.\n");
306
307 return employee;
308 }
309
310 /**
311 * @brief Gets an unassigned employee of a given type from the given base.
312 * @param[in] base Which base the employee should be hired in.
313 * @param[in] type The type of employee to search.
314 * @return Employee
315 * @sa E_EmployeeIsUnassigned
316 * @note assigned is not hired - they are already hired in a base, in a quarter _and_ working in another building.
317 */
E_GetAssignedEmployee(const base_t * const base,const employeeType_t type)318 Employee* E_GetAssignedEmployee (const base_t* const base, const employeeType_t type)
319 {
320 E_Foreach(type, employee) {
321 if (!employee->isHiredInBase(base))
322 continue;
323 if (employee->isAssigned())
324 return employee;
325 }
326 return nullptr;
327 }
328
329 /**
330 * @brief Gets an assigned employee of a given type from the given base.
331 * @param[in] base Which base the employee should be hired in.
332 * @param[in] type The type of employee to search.
333 * @return Employee
334 * @sa E_EmployeeIsUnassigned
335 * @note unassigned is not unhired - they are already hired in a base but are at quarters
336 */
E_GetUnassignedEmployee(const base_t * const base,const employeeType_t type)337 Employee* E_GetUnassignedEmployee (const base_t* const base, const employeeType_t type)
338 {
339 E_Foreach(type, employee) {
340 if (!employee->isHiredInBase(base))
341 continue;
342 if (!employee->isAssigned())
343 return employee;
344 }
345 return nullptr;
346 }
347
348 /**
349 * @brief Hires the employee in a base.
350 * @param[in] base Which base the employee should be hired in
351 * @param[in] employee Which employee to hire
352 * @sa E_HireEmployeeByType
353 * @sa E_UnhireEmployee
354 * @todo handle EMPL_ROBOT capacities here?
355 */
E_HireEmployee(base_t * base,Employee * employee)356 bool E_HireEmployee (base_t* base, Employee* employee)
357 {
358 if (CAP_GetFreeCapacity(base, CAP_EMPLOYEES) <= 0) {
359 CP_Popup(_("Not enough quarters"), _("You don't have enough quarters for your employees.\nBuild more quarters."));
360 return false;
361 }
362
363 if (employee) {
364 /* Now uses quarter space. */
365 employee->baseHired = base;
366 /* Update other capacities */
367 switch (employee->getType()) {
368 case EMPL_WORKER:
369 CAP_AddCurrent(base, CAP_EMPLOYEES, 1);
370 PR_UpdateProductionCap(base);
371 break;
372 case EMPL_PILOT:
373 AIR_AutoAddPilotToAircraft(base, employee);
374 /* fall through */
375 case EMPL_SCIENTIST:
376 case EMPL_SOLDIER:
377 CAP_AddCurrent(base, CAP_EMPLOYEES, 1);
378 break;
379 case EMPL_ROBOT:
380 CAP_AddCurrent(base, CAP_ITEMS, UGV_SIZE);
381 break;
382 case MAX_EMPL:
383 break;
384 }
385 return true;
386 }
387 return false;
388 }
389
390 /**
391 * @brief Hires the first free employee of that type.
392 * @param[in] base Which base the employee should be hired in
393 * @param[in] type Which employee type do we search
394 * @sa E_HireEmployee
395 * @sa E_UnhireEmployee
396 */
E_HireEmployeeByType(base_t * base,employeeType_t type)397 bool E_HireEmployeeByType (base_t* base, employeeType_t type)
398 {
399 Employee* employee = E_GetUnhired(type);
400 return employee ? E_HireEmployee(base, employee) : false;
401 }
402
403 /**
404 * @brief Hires the first free employee of that type.
405 * @param[in] base Which base the ugv/robot should be hired in.
406 * @param[in] ugvType What type of ugv/robot should be hired.
407 * @return true if everything went ok (the ugv was added), otherwise false.
408 */
E_HireRobot(base_t * base,const ugv_t * ugvType)409 bool E_HireRobot (base_t* base, const ugv_t* ugvType)
410 {
411 Employee* employee = E_GetUnhiredRobot(ugvType);
412 return employee ? E_HireEmployee(base, employee) : false;
413 }
414
415 /**
416 * @brief Removes the inventory of the employee and also removes him from buildings
417 */
unassign()418 base_t* Employee::unassign ()
419 {
420 /* get the base where the employee is hired in */
421 base_t* base = baseHired;
422 if (!base)
423 return base;
424
425 /* Remove employee from building/tech/production/aircraft). */
426 switch (_type) {
427 case EMPL_SCIENTIST:
428 if (isAssigned())
429 RS_RemoveFiredScientist(base, this);
430 break;
431 case EMPL_ROBOT:
432 case EMPL_SOLDIER:
433 /* Remove soldier from aircraft/team if he was assigned to one. */
434 if (AIR_IsEmployeeInAircraft(this, nullptr))
435 AIR_RemoveEmployee(this, nullptr);
436 break;
437 case EMPL_PILOT:
438 AIR_RemovePilotFromAssignedAircraft(base, this);
439 break;
440 case EMPL_WORKER:
441 /* Update current capacity and production times if worker is being counted there. */
442 PR_UpdateProductionCap(base, -1);
443 break;
444 default:
445 break;
446 }
447
448 /* Destroy the inventory of the employee (carried items will remain in base->storage) */
449 cgi->INV_DestroyInventory(&chr.inv);
450
451 /* Set all employee-tags to 'unhired'. */
452 baseHired = nullptr;
453
454 return base;
455 }
456
457 /**
458 * @brief Fires an employee.
459 * @note also remove him from the aircraft
460 * @sa E_HireEmployee
461 * @sa E_HireEmployeeByType
462 * @sa CL_RemoveSoldierFromAircraft
463 * @todo handle EMPL_ROBOT capacities here?
464 */
unhire()465 bool Employee::unhire ()
466 {
467 if (!isHired() || transfer) {
468 Com_DPrintf(DEBUG_CLIENT, "Could not fire employee\n");
469 return false;
470 }
471
472 base_t* base = unassign();
473
474 /* Remove employee from corresponding capacity */
475 switch (_type) {
476 case EMPL_PILOT:
477 case EMPL_WORKER:
478 case EMPL_SCIENTIST:
479 case EMPL_SOLDIER:
480 CAP_AddCurrent(base, CAP_EMPLOYEES, -1);
481 break;
482 case EMPL_ROBOT:
483 CAP_AddCurrent(base, CAP_ITEMS, -UGV_SIZE);
484 break;
485 case MAX_EMPL:
486 break;
487 }
488
489 return true;
490 }
491
492 /**
493 * @brief Reset the hired flag for all employees of a given type in a given base
494 * @param[in] base Which base the employee should be fired from.
495 * @param[in] type Which employee type do we search.
496 */
E_UnhireAllEmployees(base_t * base,employeeType_t type)497 void E_UnhireAllEmployees (base_t* base, employeeType_t type)
498 {
499 if (!base)
500 return;
501
502 assert(type < MAX_EMPL);
503
504 E_Foreach(type, employee) {
505 if (!employee->isHiredInBase(base))
506 continue;
507 employee->unhire();
508 }
509 }
510
511 /**
512 * @brief Creates an entry of a new employee in the global list and assignes it to no building/base.
513 * @param[in] type What type of employee to create.
514 * @param[in] nation What nation the employee (mainly used for soldiers in singleplayer) comes from.
515 * @param[in] ugvType What type of ugv this employee is.
516 * @return Pointer to the newly created employee in the global list. nullptr if something goes wrong.
517 * @sa E_DeleteEmployee
518 */
E_CreateEmployee(employeeType_t type,const nation_t * nation,const ugv_t * ugvType)519 Employee* E_CreateEmployee (employeeType_t type, const nation_t* nation, const ugv_t* ugvType)
520 {
521 const char* teamID;
522 char teamDefName[MAX_VAR];
523 const char* rank;
524
525 if (type >= MAX_EMPL)
526 return nullptr;
527
528 Employee employee(type, nation, ugvType);
529
530 teamID = GAME_GetTeamDef();
531
532 /* Generate character stats, models & names. */
533 switch (type) {
534 case EMPL_SOLDIER:
535 rank = "rifleman";
536 Q_strncpyz(teamDefName, teamID, sizeof(teamDefName));
537 break;
538 case EMPL_SCIENTIST:
539 rank = "scientist";
540 Com_sprintf(teamDefName, sizeof(teamDefName), "%s_scientist", teamID);
541 break;
542 case EMPL_PILOT:
543 rank = "pilot";
544 Com_sprintf(teamDefName, sizeof(teamDefName), "%s_pilot", teamID);
545 break;
546 case EMPL_WORKER:
547 rank = "worker";
548 Com_sprintf(teamDefName, sizeof(teamDefName), "%s_worker", teamID);
549 break;
550 case EMPL_ROBOT:
551 if (ugvType == nullptr)
552 cgi->Com_Error(ERR_DROP, "E_CreateEmployee: no type given for generation of EMPL_ROBOT employee.");
553
554 rank = "ugv";
555
556 Com_sprintf(teamDefName, sizeof(teamDefName), "%s%s", teamID, ugvType->actors);
557 break;
558 default:
559 cgi->Com_Error(ERR_DROP, "E_CreateEmployee: Unknown employee type\n");
560 }
561
562 cgi->CL_GenerateCharacter(&employee.chr, teamDefName);
563 employee.chr.score.rank = CL_GetRankIdx(rank);
564
565 Com_DPrintf(DEBUG_CLIENT, "Generate character for type: %i\n", type);
566
567 return &LIST_Add(&ccs.employees[type], employee);
568 }
569
570 /**
571 * @brief Removes the employee completely from the game (buildings + global list).
572 * @param[in] employee The pointer to the employee you want to remove.
573 * @return True if the employee was removed successfully, otherwise false.
574 * @sa E_CreateEmployee
575 * @sa E_UnhireEmployee
576 * @note This function has the side effect, that the global employee number for
577 * the given employee type is reduced by one, also the ccs.employees pointers are
578 * moved to fill the gap of the removed employee. Thus pointers like acTeam in
579 * the aircraft can point to wrong employees now. This has to be taken into
580 * account
581 */
E_DeleteEmployee(Employee * employee)582 bool E_DeleteEmployee (Employee* employee)
583 {
584 employeeType_t type;
585
586 if (!employee)
587 return false;
588
589 type = employee->getType();
590
591 /* Fire the employee. This will also:
592 * 1) remove him from buildings&work
593 * 2) remove his inventory */
594
595 if (employee->baseHired) {
596 /* make sure that this employee is really unhired */
597 employee->transfer = false;
598 employee->unhire();
599 }
600
601 /* Remove the employee from the global list. */
602 return cgi->LIST_Remove(&ccs.employees[type], (void*) employee);
603 }
604
605 /**
606 * @brief Removes all employees completely from the game (buildings + global list) from a given base.
607 * @note Used if the base e.g is destroyed by the aliens.
608 * @param[in] base Which base the employee should be fired from.
609 */
E_DeleteAllEmployees(base_t * base)610 void E_DeleteAllEmployees (base_t* base)
611 {
612 int i;
613
614 for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
615 const employeeType_t type = (employeeType_t)i;
616 E_Foreach(type, employee) {
617 if (base == nullptr || employee->isHiredInBase(base))
618 E_DeleteEmployee(employee);
619 }
620 }
621 }
622
623
624 /**
625 * @brief Removes employee until all employees fit in quarters capacity.
626 * @param[in] base Pointer to the base where the number of employees should be updated.
627 * @note employees are killed, and not just unhired (if base is destroyed, you can't recruit the same employees elsewhere)
628 * if you want to unhire employees, you should do it before calling this function.
629 * @note employees are not randomly chosen. Reason is that all Quarter will be destroyed at the same time,
630 * so all employees are going to be killed anyway.
631 */
E_DeleteEmployeesExceedingCapacity(base_t * base)632 void E_DeleteEmployeesExceedingCapacity (base_t* base)
633 {
634 int i;
635
636 /* Check if there are too many employees */
637 if (CAP_GetFreeCapacity(base, CAP_EMPLOYEES) >= 0)
638 return;
639
640 /* do a reverse loop in order to finish by soldiers (the most important employees) */
641 for (i = MAX_EMPL - 1; i >= 0; i--) {
642 const employeeType_t type = (employeeType_t)i;
643 /* UGV are not stored in Quarters */
644 if (type == EMPL_ROBOT)
645 continue;
646
647 E_Foreach(type, employee) {
648 if (employee->isHiredInBase(base))
649 E_DeleteEmployee(employee);
650
651 if (CAP_GetFreeCapacity(base, CAP_EMPLOYEES) >= 0)
652 return;
653 }
654 }
655
656 Com_Printf("E_DeleteEmployeesExceedingCapacity: Warning, removed all employees from base '%s', but capacity is still > 0\n", base->name);
657 }
658
659 /**
660 * @brief Recreates all the employees for a particular employee type in the global list.
661 * But it does not overwrite any employees already hired.
662 * @param[in] type The type of the employee list to process.
663 * @param[in] excludeUnhappyNations True if a nation is unhappy then they wont
664 * send any pilots, false if happiness of nations in not considered.
665 * @sa CP_NationHandleBudget
666 */
E_RefreshUnhiredEmployeeGlobalList(const employeeType_t type,const bool excludeUnhappyNations)667 int E_RefreshUnhiredEmployeeGlobalList (const employeeType_t type, const bool excludeUnhappyNations)
668 {
669 const nation_t* happyNations[MAX_NATIONS];
670 int numHappyNations = 0;
671 int idx, nationIdx, cnt;
672
673 happyNations[0] = nullptr;
674 /* get a list of nations, if excludeHappyNations is true then also exclude
675 * unhappy nations (unhappy nation: happiness <= 0) from the list */
676 for (idx = 0; idx < ccs.numNations; idx++) {
677 const nation_t* nation = NAT_GetNationByIDX(idx);
678 const nationInfo_t* stats = NAT_GetCurrentMonthInfo(nation);
679 if (stats->happiness > 0 || !excludeUnhappyNations) {
680 happyNations[numHappyNations] = nation;
681 numHappyNations++;
682 }
683 }
684
685 if (!numHappyNations)
686 return 0;
687
688 idx = 0;
689 /* Fill the global data employee list with employees, evenly distributed
690 * between nations in the happyNations list */
691 E_Foreach(type, employee) {
692 /* we don't want to overwrite employees that have already been hired */
693 if (!employee->isHired()) {
694 E_DeleteEmployee(employee);
695 idx++;
696 }
697 }
698
699 nationIdx = 0;
700 cnt = 0;
701 while (idx-- > 0) {
702 if (E_CreateEmployee(type, happyNations[nationIdx], nullptr) != nullptr)
703 cnt++;
704 nationIdx = (nationIdx + 1) % numHappyNations;
705 }
706
707 return cnt;
708 }
709
710 /**
711 * @brief Counts hired employees of a given type in a given base
712 * @param[in] base The base where we count (@c nullptr to count all).
713 * @param[in] type The type of employee to search.
714 * @return count of hired employees of a given type in a given base
715 */
E_CountHired(const base_t * const base,employeeType_t type)716 int E_CountHired (const base_t* const base, employeeType_t type)
717 {
718 int count = 0;
719
720 E_Foreach(type, employee) {
721 if (!employee->isHired())
722 continue;
723 if (!base || employee->isHiredInBase(base))
724 count++;
725 }
726 return count;
727 }
728
729 /**
730 * @brief Counts 'hired' (i.e. bought or produced UGVs and other robots of a given ugv-type in a given base.
731 * @param[in] base The base where we count (@c nullptr to count all).
732 * @param[in] ugvType What type of robot/ugv we are looking for.
733 * @return Count of Robots/UGVs.
734 */
E_CountHiredRobotByType(const base_t * const base,const ugv_t * ugvType)735 int E_CountHiredRobotByType (const base_t* const base, const ugv_t* ugvType)
736 {
737 int count = 0;
738
739 E_Foreach(EMPL_ROBOT, employee) {
740 if (!employee->isHired())
741 continue;
742 if (employee->getUGV() == ugvType && (!base || employee->isHiredInBase(base)))
743 count++;
744 }
745 return count;
746 }
747
748
749 /**
750 * @brief Counts all hired employees of a given base
751 * @param[in] base The base where we count
752 * @return count of hired employees of a given type in a given base
753 * @note must not return 0 if hasBuilding[B_QUARTER] is false: used to update capacity
754 * @todo What about EMPL_ROBOT?
755 */
E_CountAllHired(const base_t * const base)756 int E_CountAllHired (const base_t* const base)
757 {
758 if (!base)
759 return 0;
760
761 int count = 0;
762 for (int i = 0; i < MAX_EMPL; i++) {
763 const employeeType_t type = (employeeType_t)i;
764 count += E_CountHired(base, type);
765 }
766
767 return count;
768 }
769
770 /**
771 * @brief Counts unhired employees of a given type in a given base
772 * @param[in] type The type of employee to search.
773 * @return count of hired employees of a given type in a given base
774 */
E_CountUnhired(employeeType_t type)775 int E_CountUnhired (employeeType_t type)
776 {
777 int count = 0;
778
779 E_Foreach(type, employee) {
780 if (!employee->isHired())
781 count++;
782 }
783 return count;
784 }
785
786 /**
787 * @brief Counts all available Robots/UGVs that are for sale.
788 * @param[in] ugvType What type of robot/ugv we are looking for.
789 * @return count of available robots/ugvs.
790 */
E_CountUnhiredRobotsByType(const ugv_t * ugvType)791 int E_CountUnhiredRobotsByType (const ugv_t* ugvType)
792 {
793 int count = 0;
794
795 E_Foreach(EMPL_ROBOT, employee) {
796 if (!employee->isHired() && employee->getUGV() == ugvType)
797 count++;
798 }
799 return count;
800 }
801
802 /**
803 * @brief Counts unassigned employees of a given type in a given base
804 * @param[in] type The type of employee to search.
805 * @param[in] base The base where we count
806 */
E_CountUnassigned(const base_t * const base,employeeType_t type)807 int E_CountUnassigned (const base_t* const base, employeeType_t type)
808 {
809 if (!base)
810 return 0;
811
812 int count = 0;
813 E_Foreach(type, employee) {
814 if (!employee->isHiredInBase(base))
815 continue;
816 if (!employee->isAssigned())
817 count++;
818 }
819 return count;
820 }
821
822 /**
823 * @brief Hack to get a random nation for the initial
824 */
E_RandomNation(void)825 static inline const nation_t* E_RandomNation (void)
826 {
827 const int nationIndex = rand() % ccs.numNations;
828 return NAT_GetNationByIDX(nationIndex);
829 }
830
831 /**
832 * @brief Create initial hireable employees
833 */
E_InitialEmployees(const campaign_t * campaign)834 void E_InitialEmployees (const campaign_t* campaign)
835 {
836 int i;
837
838 /* setup initial employee count */
839 for (i = 0; i < campaign->soldiers; i++)
840 E_CreateEmployee(EMPL_SOLDIER, E_RandomNation(), nullptr);
841 for (i = 0; i < campaign->scientists; i++)
842 E_CreateEmployee(EMPL_SCIENTIST, E_RandomNation(), nullptr);
843 for (i = 0; i < campaign->workers; i++)
844 E_CreateEmployee(EMPL_WORKER, E_RandomNation(), nullptr);
845 for (i = 0; i < campaign->pilots; i++)
846 E_CreateEmployee(EMPL_PILOT, E_RandomNation(), nullptr);
847 }
848
849 #ifdef DEBUG
850 /**
851 * @brief Debug command to list all hired employee
852 */
E_ListHired_f(void)853 static void E_ListHired_f (void)
854 {
855 int i;
856
857 for (i = 0; i < MAX_EMPL; i++) {
858 const employeeType_t emplType = (employeeType_t)i;
859 E_Foreach(emplType, employee) {
860 Com_Printf("Employee: %s (ucn: %i) %s at %s\n", E_GetEmployeeString(employee->getType(), 1), employee->chr.ucn,
861 employee->chr.name, employee->baseHired->name);
862 if (employee->getType() != emplType)
863 Com_Printf("Warning: EmployeeType mismatch: %i != %i\n", emplType, employee->getType());
864 }
865 }
866 }
867
868 /**
869 * @brief Debug function to add 5 new unhired employees of each type
870 * @note called with debug_addemployees
871 */
CL_DebugNewEmployees_f(void)872 static void CL_DebugNewEmployees_f (void)
873 {
874 int j;
875 nation_t* nation = &ccs.nations[0]; /**< This is just a debugging function, nation does not matter */
876
877 for (j = 0; j < 5; j++)
878 /* Create a scientist */
879 E_CreateEmployee(EMPL_SCIENTIST, nation, nullptr);
880
881 for (j = 0; j < 5; j++)
882 /* Create a pilot. */
883 E_CreateEmployee(EMPL_PILOT, nation, nullptr);
884
885 for (j = 0; j < 5; j++)
886 /* Create a soldier. */
887 E_CreateEmployee(EMPL_SOLDIER, nation, nullptr);
888
889 for (j = 0; j < 5; j++)
890 /* Create a worker. */
891 E_CreateEmployee(EMPL_WORKER, nation, nullptr);
892 }
893 #endif
894
895 /**
896 * @brief Searches employee from a type for the ucn (character id)
897 * @param[in] type employee type
898 * @param[in] uniqueCharacterNumber unique character number (UCN)
899 */
E_GetEmployeeByTypeFromChrUCN(employeeType_t type,int uniqueCharacterNumber)900 Employee* E_GetEmployeeByTypeFromChrUCN (employeeType_t type, int uniqueCharacterNumber)
901 {
902 E_Foreach(type, employee) {
903 if (employee->chr.ucn == uniqueCharacterNumber)
904 return employee;
905 }
906
907 return nullptr;
908 }
909
910 /**
911 * @brief Searches all employee for the ucn (character id)
912 * @param[in] uniqueCharacterNumber unique character number (UCN)
913 */
E_GetEmployeeFromChrUCN(int uniqueCharacterNumber)914 Employee* E_GetEmployeeFromChrUCN (int uniqueCharacterNumber)
915 {
916 int i;
917
918 for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
919 const employeeType_t emplType = (employeeType_t)i;
920 Employee* employee = E_GetEmployeeByTypeFromChrUCN(emplType, uniqueCharacterNumber);
921 if (employee)
922 return employee;
923 }
924
925 return nullptr;
926 }
927
928
929 /**
930 * @brief Save callback for savegames in XML Format
931 * @param[out] p XML Node structure, where we write the information to
932 * @sa E_LoadXML
933 * @sa SAV_GameSaveXML
934 * @sa G_SendCharacterData
935 * @sa CP_ParseCharacterData
936 * @sa GAME_SendCurrentTeamSpawningInfo
937 */
E_SaveXML(xmlNode_t * p)938 bool E_SaveXML (xmlNode_t* p)
939 {
940 int i;
941
942 cgi->Com_RegisterConstList(saveEmployeeConstants);
943 for (i = 0; i < MAX_EMPL; i++) {
944 const employeeType_t emplType = (employeeType_t)i;
945 xmlNode_t* snode = cgi->XML_AddNode(p, SAVE_EMPLOYEE_EMPLOYEES);
946
947 cgi->XML_AddString(snode, SAVE_EMPLOYEE_TYPE, cgi->Com_GetConstVariable(SAVE_EMPLOYEETYPE_NAMESPACE, emplType));
948 E_Foreach(emplType, employee) {
949 xmlNode_t* chrNode;
950 xmlNode_t* ssnode = cgi->XML_AddNode(snode, SAVE_EMPLOYEE_EMPLOYEE);
951
952 /** @note e->transfer is not saved here because it'll be restored via TR_Load. */
953 if (employee->baseHired)
954 cgi->XML_AddInt(ssnode, SAVE_EMPLOYEE_BASEHIRED, employee->baseHired->idx);
955 if (employee->isAssigned())
956 cgi->XML_AddBool(ssnode, SAVE_EMPLOYEE_ASSIGNED, employee->isAssigned());
957 /* Store the nations identifier string. */
958 assert(employee->getNation());
959 cgi->XML_AddString(ssnode, SAVE_EMPLOYEE_NATION, employee->getNation()->id);
960 /* Store the ugv-type identifier string. (Only exists for EMPL_ROBOT). */
961 if (employee->getUGV())
962 cgi->XML_AddString(ssnode, SAVE_EMPLOYEE_UGV, employee->getUGV()->id);
963 /* Character Data */
964 chrNode = cgi->XML_AddNode(ssnode, SAVE_EMPLOYEE_CHR);
965 GAME_SaveCharacter(chrNode, &employee->chr);
966 }
967 }
968 cgi->Com_UnregisterConstList(saveEmployeeConstants);
969
970 return true;
971 }
972
973 /**
974 * @brief Load callback for savegames in XML Format
975 * @param[in] p XML Node structure, where we get the information from
976 */
E_LoadXML(xmlNode_t * p)977 bool E_LoadXML (xmlNode_t* p)
978 {
979 xmlNode_t* snode;
980 bool success = true;
981
982 cgi->Com_RegisterConstList(saveEmployeeConstants);
983 for (snode = cgi->XML_GetNode(p, SAVE_EMPLOYEE_EMPLOYEES); snode;
984 snode = cgi->XML_GetNextNode(snode, p, SAVE_EMPLOYEE_EMPLOYEES)) {
985 xmlNode_t* ssnode;
986 employeeType_t emplType;
987 const char* type = cgi->XML_GetString(snode, SAVE_EMPLOYEE_TYPE);
988
989 if (!cgi->Com_GetConstIntFromNamespace(SAVE_EMPLOYEETYPE_NAMESPACE, type, (int*) &emplType)) {
990 Com_Printf("Invalid employee type '%s'\n", type);
991 success = false;
992 break;
993 }
994
995 for (ssnode = cgi->XML_GetNode(snode, SAVE_EMPLOYEE_EMPLOYEE); ssnode;
996 ssnode = cgi->XML_GetNextNode(ssnode, snode, SAVE_EMPLOYEE_EMPLOYEE)) {
997 int baseIDX;
998 xmlNode_t* chrNode;
999
1000 /* nation */
1001 const nation_t* nation = NAT_GetNationByID(cgi->XML_GetString(ssnode, SAVE_EMPLOYEE_NATION));
1002 if (!nation) {
1003 Com_Printf("No nation defined for employee\n");
1004 success = false;
1005 break;
1006 }
1007 /* UGV-Type */
1008 const ugv_t* ugv = cgi->Com_GetUGVByIDSilent(cgi->XML_GetString(ssnode, SAVE_EMPLOYEE_UGV));
1009 Employee e(emplType, nation, ugv);
1010 /** @note e->transfer is restored in cl_transfer.c:TR_Load */
1011 /* base */
1012 assert(B_AtLeastOneExists()); /* Just in case the order is ever changed. */
1013 baseIDX = cgi->XML_GetInt(ssnode, SAVE_EMPLOYEE_BASEHIRED, -1);
1014 e.baseHired = B_GetBaseByIDX(baseIDX);
1015 /* assigned to a building? */
1016 e.setAssigned(cgi->XML_GetBool(ssnode, SAVE_EMPLOYEE_ASSIGNED, false));
1017 /* Character Data */
1018 chrNode = cgi->XML_GetNode(ssnode, SAVE_EMPLOYEE_CHR);
1019 if (!chrNode) {
1020 Com_Printf("No character definition found for employee\n");
1021 success = false;
1022 break;
1023 }
1024 if (!GAME_LoadCharacter(chrNode, &e.chr)) {
1025 Com_Printf("Error loading character definition for employee\n");
1026 success = false;
1027 break;
1028 }
1029 LIST_Add(&ccs.employees[emplType], e);
1030 }
1031 if (!success)
1032 break;
1033 }
1034 cgi->Com_UnregisterConstList(saveEmployeeConstants);
1035
1036 return success;
1037 }
1038
1039 /**
1040 * @brief Returns true if the current base is able to handle employees
1041 * @sa B_BaseInit_f
1042 */
E_HireAllowed(const base_t * base)1043 bool E_HireAllowed (const base_t* base)
1044 {
1045 if (!B_IsUnderAttack(base) && B_GetBuildingStatus(base, B_QUARTERS))
1046 return true;
1047 return false;
1048 }
1049
1050 /**
1051 * @brief Removes the items of an employee (soldier) from the base storage (s)he is hired at
1052 * @param[in] employee Pointer to the soldier whose items should be removed
1053 */
E_RemoveInventoryFromStorage(Employee * employee)1054 void E_RemoveInventoryFromStorage (Employee* employee)
1055 {
1056 const character_t* chr = &employee->chr;
1057
1058 assert(employee->baseHired);
1059
1060 const Container* cont = nullptr;
1061 while ((cont = chr->inv.getNextCont(cont))) {
1062 Item* item = nullptr;
1063 while ((item = cont->getNextItem(item))) {
1064 /* Remove ammo */
1065 if (item->ammoDef() && item->ammoDef() != item->def())
1066 B_AddToStorage(employee->baseHired, item->ammoDef(), -1);
1067 /* Remove Item */
1068 if (item->def())
1069 B_AddToStorage(employee->baseHired, item->def(), -1);
1070 }
1071 }
1072 }
1073
1074 /**
1075 * @brief This is more or less the initial
1076 * Bind some of the functions in this file to console-commands that you can call ingame.
1077 */
E_InitStartup(void)1078 void E_InitStartup (void)
1079 {
1080 E_InitCallbacks();
1081 #ifdef DEBUG
1082 cgi->Cmd_AddCommand("debug_listhired", E_ListHired_f, "Debug command to list all hired employee");
1083 cgi->Cmd_AddCommand("debug_addemployees", CL_DebugNewEmployees_f, "Debug function to add 5 new unhired employees of each type");
1084 #endif
1085 }
1086
1087 /**
1088 * @brief Closing actions for employee-subsystem
1089 */
E_Shutdown(void)1090 void E_Shutdown (void)
1091 {
1092 int i;
1093
1094 for (i = EMPL_SOLDIER; i < MAX_EMPL; i++) {
1095 const employeeType_t emplType = (employeeType_t)i;
1096 cgi->LIST_Delete(&ccs.employees[emplType]);
1097 }
1098
1099 E_ShutdownCallbacks();
1100 #ifdef DEBUG
1101 cgi->Cmd_RemoveCommand("debug_listhired");
1102 cgi->Cmd_RemoveCommand("debug_addemployees");
1103 #endif
1104 }
1105