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