1 /**
2  * @file
3  */
4 
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7 
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16 
17 See the GNU General Public License for more details.
18 
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22 
23 */
24 
25 #include "g_health.h"
26 #include "g_actor.h"
27 #include "g_client.h"
28 #include "g_combat.h"
29 #include "g_edicts.h"
30 #include "g_match.h"
31 #include "g_utils.h"
32 
G_GetImpactDirection(const Edict * const target,const vec3_t impact)33 static byte G_GetImpactDirection(const Edict* const target, const vec3_t impact)
34 {
35 	vec3_t vec1, vec2;
36 
37 	VectorSubtract(impact, target->origin, vec1);
38 	vec1[2] = 0;
39 	VectorNormalize(vec1);
40 	VectorCopy(dvecs[target->dir], vec2);
41 	VectorNormalize(vec2);
42 
43 	return AngleToDir(VectorAngleBetween(vec1, vec2) / torad);
44 }
45 
46 /**
47  * @brief Deals damage and causes wounds.
48  * @param[in,out] target Pointer to the actor we want to damage.
49  * @param[in] damage The value of the damage.
50  * @param[in] impact Impact location @c nullptr for splash damage.
51  */
G_DamageActor(Edict * target,const int damage,const vec3_t impact)52 void G_DamageActor (Edict* target, const int damage, const vec3_t impact)
53 {
54 	assert(target->chr.teamDef);
55 
56 	G_TakeDamage(target, damage);
57 	if (damage > 0 && target->HP > 0) {
58 		short bodyPart;
59 		const teamDef_t* const teamDef = target->chr.teamDef;
60 		if (impact) {
61 			/* Direct hit */
62 			const byte impactDirection = G_GetImpactDirection(target, impact);
63 			const float impactHeight = impact[2] / (target->absmin[2] + target->absmax[2]);
64 			bodyPart = teamDef->bodyTemplate->getHitBodyPart(impactDirection, impactHeight);
65 			target->chr.wounds.woundLevel[bodyPart] += damage;
66 		} else {
67 			/* No direct hit (splash damage) */
68 			int bodyPart;
69 			for (bodyPart = 0; bodyPart < teamDef->bodyTemplate->numBodyParts(); ++bodyPart)
70 				target->chr.wounds.woundLevel[bodyPart] += teamDef->bodyTemplate->getArea(bodyPart) * damage;
71 		}
72 #if 0
73 		if (!CHRSH_IsTeamDefRobot(target->chr.teamDef))
74 			/* Knockback -- currently disabled, not planned in the specs, also there's no way to tell stunned and dead actors apart */
75 			target->STUN = std::min(255.0f, target->STUN + std::max(0.0f, damage * crand() * 0.25f));
76 #endif
77 		G_SendWoundStats(target);
78 	}
79 }
80 
81 /**
82  * @brief Heals a target and treats wounds.
83  * @param[in,out] target Pointer to the actor who we want to treat.
84  * @param[in] fd Pointer to the firedef used to heal the target.
85  * @param[in] heal The value of the damage to heal.
86  * @param[in] healerTeam The index of the team of the healer.
87  */
G_TreatActor(Edict * target,const fireDef_t * const fd,const int heal,const int healerTeam)88 void G_TreatActor (Edict* target, const fireDef_t* const fd, const int heal, const int healerTeam)
89 {
90 	assert(target->chr.teamDef);
91 
92 	/* Treat wounds */
93 	if (fd->dmgweight == gi.csi->damNormal) {
94 		int bodyPart, mostWounded = 0;
95 		woundInfo_t* wounds = &target->chr.wounds;
96 
97 		/* Find the worst not treated wound */
98 		for (bodyPart = 0; bodyPart < target->chr.teamDef->bodyTemplate->numBodyParts(); ++bodyPart)
99 			if (wounds->woundLevel[bodyPart] > wounds->woundLevel[mostWounded])
100 				mostWounded = bodyPart;
101 
102 		if (wounds->woundLevel[mostWounded] > 0) {
103 			const int woundsHealed = std::min(static_cast<int>(abs(heal) / target->chr.teamDef->bodyTemplate->bleedingFactor(mostWounded)),
104 					wounds->woundLevel[mostWounded]);
105 			G_TakeDamage(target, heal);
106 			wounds->woundLevel[mostWounded] -= woundsHealed;
107 			wounds->treatmentLevel[mostWounded] += woundsHealed;
108 
109 			/* Update stats here to get info on how many HP the target received. */
110 			if (target->chr.scoreMission)
111 				target->chr.scoreMission->heal += abs(heal);
112 		}
113 	}
114 
115 	/* Treat stunned actors */
116 	if (fd->dmgweight == gi.csi->damStunElectro && G_IsStunned(target)) {
117 		if (CHRSH_IsTeamDefAlien(target->chr.teamDef) && target->team != healerTeam)
118 			/** @todo According to specs it should only be possible to use the medikit to keep an alien sedated when
119 			 * 'live alien' is researched, is it possible to find if a tech is researched here? */
120 			target->STUN = std::min(255, target->STUN - heal);
121 		else
122 			target->STUN = std::max(0, target->STUN + heal);
123 		G_ActorCheckRevitalise(target);
124 	}
125 
126 	/* Increase morale */
127 	if (fd->dmgweight == gi.csi->damShock)
128 		target->morale = std::min(GET_MORALE(target->chr.score.skills[ABILITY_MIND]), target->morale - heal);
129 
130 	G_SendWoundStats(target);
131 }
132 
133 /**
134  * @brief Deal damage to each wounded team member.
135  * @param[in] team The index of the team to deal damage to.
136  */
G_BleedWounds(const int team)137 void G_BleedWounds (const int team)
138 {
139 	Edict* ent = nullptr;
140 
141 	while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team))) {
142 		int bodyPart, damage = 0;
143 		if (CHRSH_IsTeamDefRobot(ent->chr.teamDef))
144 			continue;
145 		const teamDef_t* const teamDef = ent->chr.teamDef;
146 		woundInfo_t &wounds = ent->chr.wounds;
147 		for (bodyPart = 0; bodyPart < teamDef->bodyTemplate->numBodyParts(); ++bodyPart)
148 			if (wounds.woundLevel[bodyPart] > ent->chr.maxHP * teamDef->bodyTemplate->woundThreshold(bodyPart))
149 				damage += wounds.woundLevel[bodyPart] * teamDef->bodyTemplate->bleedingFactor(bodyPart);
150 		if (damage > 0) {
151 			G_PrintStats("%s is bleeding (damage: %i)", ent->chr.name, damage);
152 			G_TakeDamage(ent, damage);
153 			G_CheckDeathOrKnockout(ent, nullptr, nullptr, damage);
154 		}
155 	}
156 	/* Maybe the last team member bled to death */
157 	G_MatchEndCheck();
158 }
159 
160 /**
161  * @brief Send wound stats to network buffer
162  * @sa G_SendStats
163  */
G_SendWoundStats(Edict * const ent)164 void G_SendWoundStats (Edict* const ent)
165 {
166 	int i;
167 	for (i = 0; i < ent->chr.teamDef->bodyTemplate->numBodyParts(); ++i) {
168 		/* Sanity checks */
169 		woundInfo_t &wounds = ent->chr.wounds;
170 		wounds.woundLevel[i] = std::max(0, wounds.woundLevel[i]);
171 		wounds.treatmentLevel[i] = std::max(0, wounds.treatmentLevel[i]);
172 		wounds.woundLevel[i] = std::min(255, wounds.woundLevel[i]);
173 		wounds.treatmentLevel[i] = std::min(255, wounds.treatmentLevel[i]);
174 		if (wounds.woundLevel[i] + wounds.treatmentLevel[i] > 0)
175 			G_EventActorWound(*ent, i);
176 	}
177 }
178 
179 /**
180  * @brief Returns the penalty to the given stat caused by the actor wounds.
181  * @param[in] ent Pointer to the actor we want to calculate the penalty for.
182  * @param[in] type The stat we want to calculate the penalty for.
183  * @return The given penalty for this actor.
184  */
G_ActorGetInjuryPenalty(const Edict * const ent,const modifier_types_t type)185 float G_ActorGetInjuryPenalty (const Edict* const ent, const modifier_types_t type)
186 {
187 	int bodyPart;
188 	float penalty = 0;
189 
190 	const teamDef_t* const teamDef = ent->chr.teamDef;
191 	for (bodyPart = 0; bodyPart < teamDef->bodyTemplate->numBodyParts(); ++bodyPart) {
192 		const int threshold = ent->chr.maxHP * teamDef->bodyTemplate->woundThreshold(bodyPart);
193 		const int injury = (ent->chr.wounds.woundLevel[bodyPart] + ent->chr.wounds.treatmentLevel[bodyPart] * 0.5);
194 		if (injury > threshold)
195 			penalty += 2 * teamDef->bodyTemplate->penalty(bodyPart, type) * injury / ent->chr.maxHP;
196 	}
197 
198 	switch (type) {
199 	case MODIFIER_REACTION:
200 		penalty += G_ActorGetInjuryPenalty(ent, MODIFIER_SHOOTING);
201 		break;
202 	case MODIFIER_SHOOTING:
203 	case MODIFIER_ACCURACY:
204 		++penalty;
205 		break;
206 	case MODIFIER_TU:
207 	case MODIFIER_SIGHT:
208 		penalty = 1 - penalty;
209 		break;
210 	case MODIFIER_MOVEMENT:
211 		penalty = ceil(penalty);
212 		break;
213 	default:
214 		gi.DPrintf("G_ActorGetInjuryPenalty: Unknown modifier type %i\n", type);
215 		penalty = 0;
216 		break;
217 	}
218 
219 	return penalty;
220 }
221 
G_IsActorWounded(const Edict * ent)222 bool G_IsActorWounded (const Edict* ent)
223 {
224 	if (ent == nullptr || !G_IsLivingActor(ent) || ent->chr.teamDef == nullptr)
225 		return false;
226 
227 	for (int i = 0; i < ent->chr.teamDef->bodyTemplate->numBodyParts(); ++i)
228 		if (ent->chr.wounds.woundLevel[i] > 0)
229 			return true;
230 
231 	return false;
232 }
233