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