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