1 /*
2 * Copyright 2010-2014 OpenXcom Developers.
3 *
4 * This file is part of OpenXcom.
5 *
6 * OpenXcom is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OpenXcom is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OpenXcom. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "Soldier.h"
20 #include "../Engine/RNG.h"
21 #include "../Engine/Language.h"
22 #include "../Savegame/Craft.h"
23 #include "../Savegame/EquipmentLayoutItem.h"
24 #include "../Savegame/SoldierDeath.h"
25 #include "../Ruleset/SoldierNamePool.h"
26 #include "../Ruleset/RuleSoldier.h"
27 #include "../Ruleset/Armor.h"
28 #include "../Ruleset/Ruleset.h"
29 #include "../Ruleset/StatString.h"
30 #include "../Engine/Options.h"
31 #include "SavedGame.h"
32
33 namespace OpenXcom
34 {
35
36 /**
37 * Initializes a new soldier, either blank or randomly generated.
38 * @param rules Soldier ruleset.
39 * @param armor Soldier armor.
40 * @param names List of name pools for soldier generation.
41 * @param id Pointer to unique soldier id for soldier generation.
42 */
Soldier(RuleSoldier * rules,Armor * armor,const std::vector<SoldierNamePool * > * names,int id)43 Soldier::Soldier(RuleSoldier *rules, Armor *armor, const std::vector<SoldierNamePool*> *names, int id) : _name(L""), _id(id), _improvement(0), _rules(rules), _initialStats(), _currentStats(), _rank(RANK_ROOKIE), _craft(0), _gender(GENDER_MALE), _look(LOOK_BLONDE), _missions(0), _kills(0), _recovery(0), _recentlyPromoted(false), _psiTraining(false), _armor(armor), _equipmentLayout(), _death(0)
44 {
45 if (names != 0)
46 {
47 UnitStats minStats = rules->getMinStats();
48 UnitStats maxStats = rules->getMaxStats();
49
50 _initialStats.tu = RNG::generate(minStats.tu, maxStats.tu);
51 _initialStats.stamina = RNG::generate(minStats.stamina, maxStats.stamina);
52 _initialStats.health = RNG::generate(minStats.health, maxStats.health);
53 _initialStats.bravery = RNG::generate(minStats.bravery/10, maxStats.bravery/10)*10;
54 _initialStats.reactions = RNG::generate(minStats.reactions, maxStats.reactions);
55 _initialStats.firing = RNG::generate(minStats.firing, maxStats.firing);
56 _initialStats.throwing = RNG::generate(minStats.throwing, maxStats.throwing);
57 _initialStats.strength = RNG::generate(minStats.strength, maxStats.strength);
58 _initialStats.psiStrength = RNG::generate(minStats.psiStrength, maxStats.psiStrength);
59 _initialStats.melee = RNG::generate(minStats.melee, maxStats.melee);
60 _initialStats.psiSkill = minStats.psiSkill;
61
62 _currentStats = _initialStats;
63
64 if (!names->empty())
65 {
66 size_t nationality = RNG::generate(0, names->size()-1);
67 _name = names->at(nationality)->genName(&_gender);
68 _look = (SoldierLook)names->at(nationality)->genLook(4); // Once we add the ability to mod in extra looks, this will need to reference the ruleset for the maximum amount of looks.
69 }
70 else
71 {
72 _name = L"";
73 _gender = (SoldierGender)RNG::generate(0, 1);
74 _look = (SoldierLook)RNG::generate(0,3);
75 }
76 }
77 }
78
79 /**
80 *
81 */
~Soldier()82 Soldier::~Soldier()
83 {
84 for (std::vector<EquipmentLayoutItem*>::iterator i = _equipmentLayout.begin(); i != _equipmentLayout.end(); ++i)
85 {
86 delete *i;
87 }
88 delete _death;
89 }
90
91 /**
92 * Loads the soldier from a YAML file.
93 * @param node YAML node.
94 * @param rule Game ruleset.
95 * @param save Pointer to savegame.
96 */
load(const YAML::Node & node,const Ruleset * rule,SavedGame * save)97 void Soldier::load(const YAML::Node& node, const Ruleset *rule, SavedGame *save)
98 {
99 _id = node["id"].as<int>(_id);
100 _name = Language::utf8ToWstr(node["name"].as<std::string>());
101 _initialStats = node["initialStats"].as<UnitStats>(_initialStats);
102 _currentStats = node["currentStats"].as<UnitStats>(_currentStats);
103 _rank = (SoldierRank)node["rank"].as<int>();
104 _gender = (SoldierGender)node["gender"].as<int>();
105 _look = (SoldierLook)node["look"].as<int>();
106 _missions = node["missions"].as<int>(_missions);
107 _kills = node["kills"].as<int>(_kills);
108 _recovery = node["recovery"].as<int>(_recovery);
109 Armor *armor = rule->getArmor(node["armor"].as<std::string>());
110 if (armor == 0)
111 {
112 armor = rule->getArmor("STR_NONE_UC");
113 }
114 _armor = armor;
115 _psiTraining = node["psiTraining"].as<bool>(_psiTraining);
116 _improvement = node["improvement"].as<int>(_improvement);
117 if (const YAML::Node &layout = node["equipmentLayout"])
118 {
119 for (YAML::const_iterator i = layout.begin(); i != layout.end(); ++i)
120 _equipmentLayout.push_back(new EquipmentLayoutItem(*i));
121 }
122 if (node["death"])
123 {
124 _death = new SoldierDeath();
125 _death->load(node["death"]);
126 }
127 calcStatString(rule->getStatStrings(), (Options::psiStrengthEval && save->isResearched(rule->getPsiRequirements())));
128 }
129
130 /**
131 * Saves the soldier to a YAML file.
132 * @return YAML node.
133 */
save() const134 YAML::Node Soldier::save() const
135 {
136 YAML::Node node;
137 node["id"] = _id;
138 node["name"] = Language::wstrToUtf8(_name);
139 node["initialStats"] = _initialStats;
140 node["currentStats"] = _currentStats;
141 node["rank"] = (int)_rank;
142 if (_craft != 0)
143 {
144 node["craft"] = _craft->saveId();
145 }
146 node["gender"] = (int)_gender;
147 node["look"] = (int)_look;
148 node["missions"] = _missions;
149 node["kills"] = _kills;
150 if (_recovery > 0)
151 node["recovery"] = _recovery;
152 node["armor"] = _armor->getType();
153 if (_psiTraining)
154 node["psiTraining"] = _psiTraining;
155 node["improvement"] = _improvement;
156 if (!_equipmentLayout.empty())
157 {
158 for (std::vector<EquipmentLayoutItem*>::const_iterator i = _equipmentLayout.begin(); i != _equipmentLayout.end(); ++i)
159 node["equipmentLayout"].push_back((*i)->save());
160 }
161 if (_death != 0)
162 {
163 node["death"] = _death->save();
164 }
165 return node;
166 }
167
168 /**
169 * Returns the soldier's full name (and, optionally, statString).
170 * @param statstring Add stat string?
171 * @param maxLength Restrict length to a certain value.
172 * @return Soldier name.
173 */
getName(bool statstring,unsigned int maxLength) const174 std::wstring Soldier::getName(bool statstring, unsigned int maxLength) const
175 {
176 if (statstring && !_statString.empty())
177 {
178 if (_name.length() + _statString.length() > maxLength)
179 {
180 return _name.substr(0, maxLength - _statString.length()) + L"/" + _statString;
181 }
182 else
183 {
184 return _name + L"/" + _statString;
185 }
186 }
187 else
188 {
189 return _name;
190 }
191 }
192
193 /**
194 * Changes the soldier's full name.
195 * @param name Soldier name.
196 */
setName(const std::wstring & name)197 void Soldier::setName(const std::wstring &name)
198 {
199 _name = name;
200 }
201
202 /**
203 * Returns the craft the soldier is assigned to.
204 * @return Pointer to craft.
205 */
getCraft() const206 Craft *Soldier::getCraft() const
207 {
208 return _craft;
209 }
210
211 /**
212 * Assigns the soldier to a new craft.
213 * @param craft Pointer to craft.
214 */
setCraft(Craft * craft)215 void Soldier::setCraft(Craft *craft)
216 {
217 _craft = craft;
218 }
219
220 /**
221 * Returns the soldier's craft string, which
222 * is either the soldier's wounded status,
223 * the assigned craft name, or none.
224 * @param lang Language to get strings from.
225 * @return Full name.
226 */
getCraftString(Language * lang) const227 std::wstring Soldier::getCraftString(Language *lang) const
228 {
229 std::wstring s;
230 if (_recovery > 0)
231 {
232 s = lang->getString("STR_WOUNDED");
233 }
234 else if (_craft == 0)
235 {
236 s = lang->getString("STR_NONE_UC");
237 }
238 else
239 {
240 s = _craft->getName(lang);
241 }
242 return s;
243 }
244
245 /**
246 * Returns a localizable-string representation of
247 * the soldier's military rank.
248 * @return String ID for rank.
249 */
getRankString() const250 std::string Soldier::getRankString() const
251 {
252 switch (_rank)
253 {
254 case RANK_ROOKIE:
255 return "STR_ROOKIE";
256 case RANK_SQUADDIE:
257 return "STR_SQUADDIE";
258 case RANK_SERGEANT:
259 return "STR_SERGEANT";
260 case RANK_CAPTAIN:
261 return "STR_CAPTAIN";
262 case RANK_COLONEL:
263 return "STR_COLONEL";
264 case RANK_COMMANDER:
265 return "STR_COMMANDER";
266 default:
267 return "";
268 }
269 }
270
271 /**
272 * Returns a graphic representation of
273 * the soldier's military rank.
274 * @note THE MEANING OF LIFE
275 * @return Sprite ID for rank.
276 */
getRankSprite() const277 int Soldier::getRankSprite() const
278 {
279 return 42 + _rank;
280 }
281
282
283 /**
284 * Returns the soldier's military rank.
285 * @return Rank enum.
286 */
getRank() const287 SoldierRank Soldier::getRank() const
288 {
289 return _rank;
290 }
291
292 /**
293 * Increase the soldier's military rank.
294 */
promoteRank()295 void Soldier::promoteRank()
296 {
297 _rank = (SoldierRank)((int)_rank + 1);
298 if (_rank > RANK_SQUADDIE)
299 {
300 // only promotions above SQUADDIE are worth to be mentioned
301 _recentlyPromoted = true;
302 }
303 }
304
305 /**
306 * Returns the soldier's amount of missions.
307 * @return Missions.
308 */
getMissions() const309 int Soldier::getMissions() const
310 {
311 return _missions;
312 }
313
314 /**
315 * Returns the soldier's amount of kills.
316 * @return Kills.
317 */
getKills() const318 int Soldier::getKills() const
319 {
320 return _kills;
321 }
322
323 /**
324 * Returns the soldier's gender.
325 * @return Gender.
326 */
getGender() const327 SoldierGender Soldier::getGender() const
328 {
329 return _gender;
330 }
331
332 /**
333 * Returns the soldier's look.
334 * @return Look.
335 */
getLook() const336 SoldierLook Soldier::getLook() const
337 {
338 return _look;
339 }
340
341 /**
342 * Returns the soldier's rules.
343 * @return rulesoldier
344 */
getRules() const345 RuleSoldier *Soldier::getRules() const
346 {
347 return _rules;
348 }
349
350 /**
351 * Returns the soldier's unique ID. Each soldier
352 * can be identified by its ID. (not it's name)
353 * @return Unique ID.
354 */
getId() const355 int Soldier::getId() const
356 {
357 return _id;
358 }
359
360 /**
361 * Add a mission to the counter.
362 */
addMissionCount()363 void Soldier::addMissionCount()
364 {
365 _missions++;
366 }
367
368 /**
369 * Add a kill to the counter.
370 */
addKillCount(int count)371 void Soldier::addKillCount(int count)
372 {
373 _kills += count;
374 }
375
376 /**
377 * Get pointer to initial stats.
378 */
getInitStats()379 UnitStats *Soldier::getInitStats()
380 {
381 return &_initialStats;
382 }
383
384 /**
385 * Get pointer to current stats.
386 */
getCurrentStats()387 UnitStats *Soldier::getCurrentStats()
388 {
389 return &_currentStats;
390 }
391
392 /**
393 * Returns the unit's promotion status and resets it.
394 * @return True if recently promoted, False otherwise.
395 */
isPromoted()396 bool Soldier::isPromoted()
397 {
398 bool promoted = _recentlyPromoted;
399 _recentlyPromoted = false;
400 return promoted;
401 }
402
403 /**
404 * Returns the unit's current armor.
405 * @return Pointer to armor data.
406 */
getArmor() const407 Armor *Soldier::getArmor() const
408 {
409 return _armor;
410 }
411
412 /**
413 * Changes the unit's current armor.
414 * @param armor Pointer to armor data.
415 */
setArmor(Armor * armor)416 void Soldier::setArmor(Armor *armor)
417 {
418 _armor = armor;
419 }
420
421 /**
422 * Returns the amount of time until the soldier is healed.
423 * @return Number of days.
424 */
getWoundRecovery() const425 int Soldier::getWoundRecovery() const
426 {
427 return _recovery;
428 }
429
430 /**
431 * Changes the amount of time until the soldier is healed.
432 * @param recovery Number of days.
433 */
setWoundRecovery(int recovery)434 void Soldier::setWoundRecovery(int recovery)
435 {
436 _recovery = recovery;
437
438 // dismiss from craft
439 if (_recovery > 0)
440 {
441 _craft = 0;
442 }
443 }
444
445 /**
446 * Heals soldier wounds.
447 */
heal()448 void Soldier::heal()
449 {
450 _recovery--;
451 }
452
453 /**
454 * Returns the list of EquipmentLayoutItems of a soldier.
455 * @return Pointer to the EquipmentLayoutItem list.
456 */
getEquipmentLayout()457 std::vector<EquipmentLayoutItem*> *Soldier::getEquipmentLayout()
458 {
459 return &_equipmentLayout;
460 }
461
462 /**
463 * Trains a soldier's Psychic abilities after 1 month.
464 */
trainPsi()465 void Soldier::trainPsi()
466 {
467 _improvement = 0;
468 // -10 days - tolerance threshold for switch from anytimePsiTraining option.
469 // If soldier has psiskill -10..-1, he was trained 20..59 days. 81.7% probability, he was trained more that 30 days.
470 if (_currentStats.psiSkill < -10 + _rules->getMinStats().psiSkill)
471 _currentStats.psiSkill = _rules->getMinStats().psiSkill;
472 else if(_currentStats.psiSkill <= _rules->getMaxStats().psiSkill)
473 {
474 int max = _rules->getMaxStats().psiSkill + _rules->getMaxStats().psiSkill / 2;
475 _improvement = RNG::generate(_rules->getMaxStats().psiSkill, max);
476 }
477 else if(_currentStats.psiSkill <= (_rules->getStatCaps().psiSkill / 2))
478 _improvement = RNG::generate(5, 12);
479 else if(_currentStats.psiSkill < _rules->getStatCaps().psiSkill)
480 _improvement = RNG::generate(1, 3);
481 _currentStats.psiSkill += _improvement;
482 if(_currentStats.psiSkill > 100)
483 _currentStats.psiSkill = 100;
484 }
485
486 /**
487 * Trains a soldier's Psychic abilities after 1 day.
488 * (anytimePsiTraining option)
489 */
trainPsi1Day()490 void Soldier::trainPsi1Day()
491 {
492 if (!_psiTraining)
493 {
494 _improvement = 0;
495 return;
496 }
497
498 if (_currentStats.psiSkill > 0) // yes, 0. _rules->getMinStats().psiSkill was wrong.
499 {
500 if (8 * 100 >= _currentStats.psiSkill * RNG::generate(1, 100) && _currentStats.psiSkill < _rules->getStatCaps().psiSkill)
501 {
502 ++_improvement;
503 ++_currentStats.psiSkill;
504 }
505 }
506 else if (_currentStats.psiSkill < _rules->getMinStats().psiSkill)
507 {
508 if (++_currentStats.psiSkill == _rules->getMinStats().psiSkill) // initial training is over
509 {
510 _improvement = _rules->getMaxStats().psiSkill + RNG::generate(0, _rules->getMaxStats().psiSkill / 2);
511 _currentStats.psiSkill = _improvement;
512 }
513 }
514 else // minStats.psiSkill <= 0 && _currentStats.psiSkill == minStats.psiSkill
515 _currentStats.psiSkill -= RNG::generate(30, 60); // set initial training from 30 to 60 days
516 }
517
518 /**
519 * returns whether or not the unit is in psi training
520 * @return true/false
521 */
isInPsiTraining()522 bool Soldier::isInPsiTraining()
523 {
524 return _psiTraining;
525 }
526
527 /**
528 * toggles whether or not the unit is in psi training
529 */
setPsiTraining()530 void Soldier::setPsiTraining()
531 {
532 _psiTraining = !_psiTraining;
533 }
534
535 /**
536 * returns this soldier's psionic improvement score for this month.
537 * @return score
538 */
getImprovement()539 int Soldier::getImprovement()
540 {
541 return _improvement;
542 }
543
544 /**
545 * Returns the soldier's death details.
546 * @return Pointer to death data. NULL if no death has occured.
547 */
getDeath() const548 SoldierDeath *Soldier::getDeath() const
549 {
550 return _death;
551 }
552
553 /**
554 * Kills the soldier in the Geoscape.
555 * @param death Pointer to death data.
556 */
die(SoldierDeath * death)557 void Soldier::die(SoldierDeath *death)
558 {
559 delete _death;
560 _death = death;
561
562 // Clean up associations
563 _craft = 0;
564 _psiTraining = false;
565 _recentlyPromoted = false;
566 _recovery = 0;
567 for (std::vector<EquipmentLayoutItem*>::iterator i = _equipmentLayout.begin(); i != _equipmentLayout.end(); ++i)
568 {
569 delete *i;
570 }
571 _equipmentLayout.clear();
572 }
573
574 /**
575 * Calculates the soldier's statString.
576 * @param statStrings List of statString rules.
577 * @param psiStrengthEval Are psi stats available?
578 */
calcStatString(const std::vector<StatString * > & statStrings,bool psiStrengthEval)579 void Soldier::calcStatString(const std::vector<StatString *> &statStrings, bool psiStrengthEval)
580 {
581 _statString = StatString::calcStatString(_currentStats, statStrings, psiStrengthEval);
582 }
583
584 }
585