1 /**
2  * @file
3  * @note Shared character generating functions prefix: CHRSH_
4  */
5 
6 /*
7 Copyright (C) 2002-2013 UFO: Alien Invasion.
8 
9 This program is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public License
11 as published by the Free Software Foundation; either version 2
12 of the License, or (at your option) any later version.
13 
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 
18 See the GNU General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
23 
24 */
25 
26 #include "q_shared.h"
27 #include "chr_shared.h"
28 
character_s()29 character_s::character_s ()
30 {
31 	init();
32 }
33 
init()34 void character_s::init ()
35 {
36 	name[0] = path[0] = body[0] = head[0] = '\0';
37 	ucn = bodySkin = headSkin = HP = minHP = maxHP = STUN = morale = state = gender = 0;
38 	fieldSize = 0;
39 	scoreMission = nullptr;
40 	teamDef = nullptr;
41 	inv.init();
42 	memset(implants, 0, sizeof(implants));
43 }
44 
45 /**
46  * @brief Check if a team definition is alien.
47  * @param[in] td Pointer to the team definition to check.
48  */
CHRSH_IsTeamDefAlien(const teamDef_t * const td)49 bool CHRSH_IsTeamDefAlien (const teamDef_t* const td)
50 {
51 	return td->team == TEAM_ALIEN;
52 }
53 
CHRSH_IsArmourUseableForTeam(const objDef_t * od,const teamDef_t * teamDef)54 bool CHRSH_IsArmourUseableForTeam (const objDef_t* od, const teamDef_t* teamDef)
55 {
56 	assert(teamDef);
57 	assert(od->isArmour());
58 
59 	if (!teamDef->armour)
60 		return false;
61 
62 	return od->useable == teamDef->team;
63 }
64 
65 /**
66  * @brief Check if a team definition is a robot.
67  * @param[in] td Pointer to the team definition to check.
68  */
CHRSH_IsTeamDefRobot(const teamDef_t * const td)69 bool CHRSH_IsTeamDefRobot (const teamDef_t* const td)
70 {
71 	return td->robot;
72 }
73 
CHRSH_GetTemplateByID(const teamDef_t * teamDef,const char * templateId)74 const chrTemplate_t* CHRSH_GetTemplateByID (const teamDef_t* teamDef, const char* templateId) {
75 	if (!Q_strnull(templateId))
76 		for (int i = 0; i < teamDef->numTemplates; i++)
77 			if (Q_streq(teamDef->characterTemplates[i]->id, templateId))
78 				return teamDef->characterTemplates[i];
79 
80 	return nullptr;
81 }
82 
83 /**
84  * @brief Assign the effect values to the character
85  */
CHRSH_UpdateCharacterWithEffect(character_t & chr,const itemEffect_t & e)86 static void CHRSH_UpdateCharacterWithEffect (character_t& chr, const itemEffect_t& e)
87 {
88 	chrScoreGlobal_t& s = chr.score;
89 	if (fabs(e.accuracy) > EQUAL_EPSILON)
90 		s.skills[ABILITY_ACCURACY] *= 1.0f + e.accuracy;
91 	if (fabs(e.mind) > EQUAL_EPSILON)
92 		s.skills[ABILITY_MIND] *= 1.0f + e.mind;
93 	if (fabs(e.power) > EQUAL_EPSILON)
94 		s.skills[ABILITY_POWER] *= 1.0f + e.power;
95 	if (fabs(e.TUs) > EQUAL_EPSILON)
96 		s.skills[ABILITY_SPEED] *= 1.0f + e.TUs;
97 
98 	if (fabs(e.morale) > EQUAL_EPSILON)
99 		chr.morale *= 1.0f + e.morale;
100 }
101 
102 /**
103  * @brief Updates the characters permanent implants. Called every day.
104  */
CHRSH_UpdateImplants(character_t & chr)105 void CHRSH_UpdateImplants (character_t& chr)
106 {
107 	for (int i = 0; i < lengthof(chr.implants); i++) {
108 		implant_t& implant = chr.implants[i];
109 		const implantDef_t* def = implant.def;
110 		/* empty slot */
111 		if (def == nullptr || def->item == nullptr)
112 			continue;
113 		const objDef_t& od = *def->item;
114 		const itemEffect_t* e = od.strengthenEffect;
115 
116 		if (implant.installedTime > 0) {
117 			implant.installedTime--;
118 			if (implant.installedTime == 0 && e != nullptr && e->isPermanent) {
119 				CHRSH_UpdateCharacterWithEffect(chr, *e);
120 			}
121 		}
122 
123 		if (implant.removedTime > 0) {
124 			implant.removedTime--;
125 			if (implant.removedTime == 0) {
126 				implant.def = nullptr;
127 				continue;
128 			}
129 		}
130 		if (e == nullptr || e->period <= 0)
131 			continue;
132 
133 		implant.trigger--;
134 		if (implant.trigger <= 0)
135 			continue;
136 
137 		CHRSH_UpdateCharacterWithEffect(chr, *e);
138 		implant.trigger = e->period;
139 	}
140 }
141 
142 /**
143  * @brief Add a new implant to a character
144  */
CHRSH_ApplyImplant(character_t & chr,const implantDef_t & def)145 const implant_t* CHRSH_ApplyImplant (character_t& chr, const implantDef_t& def)
146 {
147 	const objDef_t& od = *def.item;
148 
149 	if (!od.implant) {
150 		Com_Printf("object '%s' is no implant\n", od.id);
151 		return nullptr;
152 	}
153 
154 	const itemEffect_t* e = od.strengthenEffect;
155 	if (e != nullptr && e->period <= 0 && !e->isPermanent) {
156 		Com_Printf("object '%s' is not permanent\n", od.id);
157 		return nullptr;
158 	}
159 
160 	for (int i = 0; i < lengthof(chr.implants); i++) {
161 		implant_t& implant = chr.implants[i];
162 		/* already filled slot */
163 		if (implant.def != nullptr)
164 			continue;
165 
166 		OBJZERO(implant);
167 		implant.def = &def;
168 		if (e != nullptr && !e->isPermanent)
169 			implant.trigger = e->period;
170 		implant.installedTime = def.installationTime;
171 
172 		return &chr.implants[i];
173 	}
174 	Com_Printf("no free implant slot\n");
175 	return nullptr;
176 }
177 
178 /**
179  * @brief Generates a skill and ability set for any character.
180  * @param[in] chr Pointer to the character, for which we generate stats.
181  * @param[in] multiplayer If this is true we use the skill values from @c soldier_mp
182  * @note mulitplayer is a special case here
183  * @todo Add modifiers for difficulty setting here!
184  */
CHRSH_CharGenAbilitySkills(character_t * chr,bool multiplayer,const char * templateId)185 void CHRSH_CharGenAbilitySkills (character_t* chr, bool multiplayer, const char* templateId)
186 {
187 	const chrTemplate_t* chrTemplate;
188 	const teamDef_t* teamDef = chr->teamDef;
189 
190 	if (multiplayer && teamDef->team == TEAM_PHALANX)
191 		/* @todo Hard coded template id, remove when possible */
192 		templateId = "soldier_mp";
193 
194 	if (!Q_strnull(templateId)) {
195 		chrTemplate = CHRSH_GetTemplateByID(teamDef, templateId);
196 		if (!chrTemplate)
197 			Sys_Error("CHRSH_CharGenAbilitySkills: Character template not found (%s) in %s", templateId, teamDef->id);
198 	} else if (teamDef->characterTemplates[0]) {
199 		if (teamDef->numTemplates > 1) {
200 			float sumRate = 0.0f;
201 			for (int i = 0; i < teamDef->numTemplates; i++) {
202 				chrTemplate = teamDef->characterTemplates[i];
203 				sumRate += chrTemplate->rate;
204 			}
205 			if (sumRate > 0.0f) {
206 				const float soldierRoll = frand();
207 				float curRate = 0.0f;
208 				for (chrTemplate = teamDef->characterTemplates[0]; chrTemplate->id; chrTemplate++) {
209 					curRate += chrTemplate->rate;
210 					if (curRate && soldierRoll <= (curRate / sumRate))
211 						break;
212 				}
213 			} else {
214 				/* No rates or all set to 0 default to first template */
215 				chrTemplate = teamDef->characterTemplates[0];
216 			}
217 		} else {
218 			/* Only one template */
219 			chrTemplate = teamDef->characterTemplates[0];
220 		}
221 	} else {
222 		Sys_Error("CHRSH_CharGenAbilitySkills: No character template for team %s!", teamDef->id);
223 	}
224 
225 	assert(chrTemplate);
226 	const int (*skillsTemplate)[2] = chrTemplate->skills;
227 
228 	/* Abilities and skills -- random within the range */
229 	for (int i = 0; i < SKILL_NUM_TYPES; i++) {
230 		const int abilityWindow = skillsTemplate[i][1] - skillsTemplate[i][0];
231 		/* Reminder: In case if abilityWindow==0 the ability will be set to the lower limit. */
232 		const int temp = (frand() * abilityWindow) + skillsTemplate[i][0];
233 		chr->score.skills[i] = temp;
234 		chr->score.initialSkills[i] = temp;
235 	}
236 
237 	/* Health. */
238 	const int abilityWindow = skillsTemplate[SKILL_NUM_TYPES][1] - skillsTemplate[SKILL_NUM_TYPES][0];
239 	const int temp = (frand() * abilityWindow) + skillsTemplate[SKILL_NUM_TYPES][0];
240 	chr->score.initialSkills[SKILL_NUM_TYPES] = temp;
241 	chr->maxHP = temp;
242 	chr->HP = temp;
243 
244 	/* Morale */
245 	chr->morale = GET_MORALE(chr->score.skills[ABILITY_MIND]);
246 	if (chr->morale >= MAX_SKILL)
247 		chr->morale = MAX_SKILL;
248 
249 	/* Initialize the experience values */
250 	for (int i = 0; i <= SKILL_NUM_TYPES; i++) {
251 		chr->score.experience[i] = 0;
252 	}
253 }
254 
255 /**
256  * @brief Returns the body model for the soldiers for armoured and non armoured soldiers
257  * @param[in] chr Pointer to character struct
258  * @sa CHRSH_CharGetBody
259  * @return the character body model (from a static buffer)
260  */
CHRSH_CharGetBody(const character_t * const chr)261 const char* CHRSH_CharGetBody (const character_t* const chr)
262 {
263 	static char returnModel[MAX_VAR];
264 
265 	/* models of UGVs don't change - because they are already armoured */
266 	if (chr->inv.getArmour() && !CHRSH_IsTeamDefRobot(chr->teamDef)) {
267 		const objDef_t* od = chr->inv.getArmour()->def();
268 		const char* id = od->armourPath;
269 		if (!od->isArmour())
270 			Sys_Error("CHRSH_CharGetBody: Item is no armour");
271 
272 		Com_sprintf(returnModel, sizeof(returnModel), "%s%s/%s", chr->path, id, chr->body);
273 	} else
274 		Com_sprintf(returnModel, sizeof(returnModel), "%s/%s", chr->path, chr->body);
275 	return returnModel;
276 }
277 
278 /**
279  * @brief Returns the head model for the soldiers for armoured and non armoured soldiers
280  * @param[in] chr Pointer to character struct
281  * @sa CHRSH_CharGetBody
282  */
CHRSH_CharGetHead(const character_t * const chr)283 const char* CHRSH_CharGetHead (const character_t* const chr)
284 {
285 	static char returnModel[MAX_VAR];
286 
287 	/* models of UGVs don't change - because they are already armoured */
288 	if (chr->inv.getArmour() && !chr->teamDef->robot) {
289 		const objDef_t* od = chr->inv.getArmour()->def();
290 		const char* id = od->armourPath;
291 		if (!od->isArmour())
292 			Sys_Error("CHRSH_CharGetBody: Item is no armour");
293 
294 		Com_sprintf(returnModel, sizeof(returnModel), "%s%s/%s", chr->path, id, chr->head);
295 	} else
296 		Com_sprintf(returnModel, sizeof(returnModel), "%s/%s", chr->path, chr->head);
297 	return returnModel;
298 }
299 
BodyData(void)300 BodyData::BodyData (void) :
301 		_totalBodyArea(0.0f), _numBodyParts(0)
302 {
303 }
304 
getRandomBodyPart(void) const305 short BodyData::getRandomBodyPart (void) const
306 {
307 	const float rnd = frand() * _totalBodyArea;
308 	float currentArea = 0.0f;
309 	short bodyPart;
310 
311 	for (bodyPart = 0; bodyPart < _numBodyParts; ++bodyPart) {
312 		currentArea += getArea(bodyPart);
313 		if (rnd <= currentArea)
314 			break;
315 	}
316 	if (bodyPart >= _numBodyParts) {
317 		bodyPart = 0;
318 		Com_DPrintf(DEBUG_SHARED, "Warning: No bodypart hit, defaulting to %s!\n", name(bodyPart));
319 	}
320 	return bodyPart;
321 }
322 
id(void) const323 const char* BodyData::id (void) const
324 {
325 	return _id;
326 }
327 
id(const short bodyPart) const328 const char* BodyData::id (const short bodyPart) const
329 {
330 	return _bodyParts[bodyPart].id;
331 }
332 
name(const short bodyPart) const333 const char* BodyData::name (const short bodyPart) const
334 {
335 	return _bodyParts[bodyPart].name;
336 }
337 
penalty(const short bodyPart,const modifier_types_t type) const338 float BodyData::penalty (const short bodyPart, const modifier_types_t type) const
339 {
340 	return _bodyParts[bodyPart].penalties[type] * 0.01f;
341 }
342 
bleedingFactor(const short bodyPart) const343 float BodyData::bleedingFactor (const short bodyPart) const
344 {
345 	return _bodyParts[bodyPart].bleedingFactor * 0.01f;
346 }
347 
woundThreshold(const short bodyPart) const348 float BodyData::woundThreshold (const short bodyPart) const
349 {
350 	return _bodyParts[bodyPart].woundThreshold * 0.01f;
351 }
352 
numBodyParts(void) const353 short BodyData::numBodyParts (void) const
354 {
355 	return _numBodyParts;
356 }
357 
setId(const char * id)358 void BodyData::setId (const char* id)
359 {
360 	Q_strncpyz(_id, id, sizeof(_id));
361 }
362 
addBodyPart(const BodyPartData & bodyPart)363 void BodyData::addBodyPart (const BodyPartData &bodyPart)
364 {
365 	_bodyParts[_numBodyParts] = bodyPart;
366 	_totalBodyArea += getArea(_numBodyParts++);
367 }
368 
getHitBodyPart(const byte direction,const float height) const369 short BodyData::getHitBodyPart (const byte direction, const float height) const
370 {
371 	const float rnd = frand();
372 	short bodyPart;
373 	float curRand = 0;
374 	vec4_t shape;
375 
376 	for (bodyPart = 0; bodyPart < _numBodyParts; ++bodyPart) {
377 		Vector4Copy(_bodyParts[bodyPart].shape, shape);
378 		if (height <= shape[3] || height > shape[2] + shape[3])
379 			continue;
380 		curRand += (direction < 2 ? shape[0] : (direction < 4 ? shape[1] : (shape[0] + shape[1]) * 0.5f));
381 		if (rnd <= curRand)
382 			break;
383 	}
384 	if (bodyPart >= _numBodyParts) {
385 		bodyPart = 0;
386 		Com_DPrintf(DEBUG_SHARED, "Warning: No bodypart hit, defaulting to %s!\n", name(bodyPart));
387 	}
388 	return bodyPart;
389 }
390 
getArea(const short bodyPart) const391 float BodyData::getArea(const short bodyPart) const
392 {
393 	return (_bodyParts[bodyPart].shape[0] + _bodyParts[bodyPart].shape[1]) * 0.5f * _bodyParts[bodyPart].shape[2];
394 }
395