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_local.h"
26 #include "g_actor.h"
27 #include "g_client.h"
28 #include "g_edicts.h"
29 #include "g_health.h"
30 #include "g_inventory.h"
31 #include "g_utils.h"
32 #include "g_vis.h"
33
34 /**
35 * @brief Checks whether a point is "visible" from the edicts position
36 * @sa FrustumVis
37 */
G_FrustumVis(const Edict * from,const vec3_t point)38 bool G_FrustumVis (const Edict* from, const vec3_t point)
39 {
40 if (G_IsActiveCamera(from)) {
41 /* it's a 360 degree camera */
42 if (from->camera.rotate)
43 return true;
44 }
45 return FrustumVis(from->origin, from->dir, point);
46 }
47
48 /**
49 * @brief tests the visibility between two points
50 * @param[in] from The point to check visibility from
51 * @param[in] to The point to check visibility to
52 * @return true if the points are invisible from each other (trace hit something), false otherwise.
53 */
G_LineVis(const vec3_t from,const vec3_t to)54 static bool G_LineVis (const vec3_t from, const vec3_t to)
55 {
56 return G_TestLineWithEnts(from, to);
57 }
58
59 /**
60 * @brief calculate how much check is "visible" from @c from
61 * @param[in] from The world coordinate to check from
62 * @param[in] ent The source edict of the check
63 * @param[in] check The edict to check how good (or if at all) it is visible
64 * @param[in] full Perform a full check in different directions. If this is
65 * @c false the actor is fully visible if one vis check returned @c true. With
66 * @c true this function can also return a value != 0.0 and != 1.0. Try to only
67 * use @c true if you really need the full check. Full checks are of course
68 * more expensive.
69 * @return a value between 0.0 and 1.0 which reflects the visibility from 0
70 * to 100 percent
71 * @note This call isn't cheap - try to do this only if you really need the
72 * visibility check or the statement whether one particular actor see another
73 * particular actor.
74 * @sa CL_ActorVis
75 */
G_ActorVis(const vec3_t from,const Edict * ent,const Edict * check,bool full)76 float G_ActorVis (const vec3_t from, const Edict* ent, const Edict* check, bool full)
77 {
78 vec3_t test, dir;
79 float delta;
80 int i, n;
81 const float distance = VectorDist(check->origin, ent->origin);
82
83 /* units that are very close are visible in the smoke */
84 if (distance > UNIT_SIZE * 1.5f) {
85 vec3_t eyeEnt;
86 Edict* e = nullptr;
87
88 G_ActorGetEyeVector(ent, eyeEnt);
89
90 while ((e = G_EdictsGetNextInUse(e))) {
91 if (G_IsSmoke(e)) {
92 if (RayIntersectAABB(eyeEnt, check->absmin, e->absmin, e->absmax)
93 || RayIntersectAABB(eyeEnt, check->absmax, e->absmin, e->absmax)) {
94 return ACTOR_VIS_0;
95 }
96 }
97 }
98 }
99
100 /* start on eye height */
101 VectorCopy(check->origin, test);
102 if (G_IsDead(check)) {
103 test[2] += PLAYER_DEAD;
104 delta = 0;
105 } else if (G_IsCrouched(check)) {
106 test[2] += PLAYER_CROUCH - 2;
107 delta = (PLAYER_CROUCH - PLAYER_MIN) / 2 - 2;
108 } else {
109 test[2] += PLAYER_STAND;
110 delta = (PLAYER_STAND - PLAYER_MIN) / 2 - 2;
111 }
112
113 /* side shifting -> better checks */
114 dir[0] = from[1] - check->origin[1];
115 dir[1] = check->origin[0] - from[0];
116 dir[2] = 0;
117 VectorNormalizeFast(dir);
118 VectorMA(test, -7, dir, test);
119
120 /* do 3 tests */
121 n = 0;
122 for (i = 0; i < 3; i++) {
123 if (!G_LineVis(from, test)) {
124 if (full)
125 n++;
126 else
127 return ACTOR_VIS_100;
128 }
129
130 /* look further down or stop */
131 if (!delta) {
132 if (n > 0)
133 return ACTOR_VIS_100;
134 else
135 return ACTOR_VIS_0;
136 }
137 VectorMA(test, 7, dir, test);
138 test[2] -= delta;
139 }
140
141 /* return factor */
142 switch (n) {
143 case 0:
144 return ACTOR_VIS_0;
145 case 1:
146 return ACTOR_VIS_10;
147 case 2:
148 return ACTOR_VIS_50;
149 default:
150 return ACTOR_VIS_100;
151 }
152 }
153
G_VisCheckDist(const Edict * const ent)154 int G_VisCheckDist (const Edict* const ent)
155 {
156 if (G_IsActiveCamera(ent))
157 return MAX_SPOT_DIST_CAMERA;
158
159 if (G_IsActor(ent))
160 return MAX_SPOT_DIST * G_ActorGetInjuryPenalty(ent, MODIFIER_SIGHT);
161
162 return MAX_SPOT_DIST;
163 }
164
165 /**
166 * @brief test if check is visible by from
167 * @param[in] team Living team members are always visible. If this is a negative
168 * number we inverse the team rules (see comments included). In combination with VT_NOFRUSTUM
169 * we can check whether there is any edict (that is no in our team) that can see @c check
170 * @param[in] from is from team @c team and must be a living actor
171 * @param[in] check The edict we want to get the visibility for
172 * @param[in] flags @c VT_NOFRUSTUM, ...
173 */
G_Vis(const int team,const Edict * from,const Edict * check,const vischeckflags_t flags)174 bool G_Vis (const int team, const Edict* from, const Edict* check, const vischeckflags_t flags)
175 {
176 vec3_t eye;
177
178 /* if any of them isn't in use, then they're not visible */
179 if (!from->inuse || !check->inuse)
180 return false;
181
182 /* only actors and 2x2 units can see anything */
183 if (!G_IsLivingActor(from) && !G_IsActiveCamera(from))
184 return false;
185
186 /* living team members are always visible */
187 if (team >= 0 && check->team == team && !G_IsDead(check))
188 return true;
189
190 /* standard team rules */
191 if (team >= 0 && from->team != team)
192 return false;
193
194 /* inverse team rules */
195 if (team < 0 && check->team == -team)
196 return false;
197
198 /* check for same pos */
199 if (VectorCompare(from->pos, check->pos))
200 return true;
201
202 if (!G_IsVisibleOnBattlefield(check))
203 return false;
204
205 /* view distance check */
206 const int spotDist = G_VisCheckDist(from);
207 if (VectorDistSqr(from->origin, check->origin) > spotDist * spotDist)
208 return false;
209
210 /* view frustum check */
211 if (!(flags & VT_NOFRUSTUM) && !G_FrustumVis(from, check->origin))
212 return false;
213
214 /* get viewers eye height */
215 G_ActorGetEyeVector(from, eye);
216
217 /* line trace check */
218 switch (check->type) {
219 case ET_ACTOR:
220 case ET_ACTOR2x2:
221 return G_ActorVis(eye, from, check, false) > ACTOR_VIS_0;
222 case ET_ITEM:
223 case ET_CAMERA:
224 case ET_PARTICLE:
225 return !G_LineVis(eye, check->origin);
226 default:
227 return false;
228 }
229 }
230
231 /**
232 * @brief test if @c check is visible by team (or if visibility changed?)
233 * @sa G_CheckVisTeam
234 * @param[in] team the team the edict may become visible for or perish from
235 * their view
236 * @param[in] check the edict we are talking about - which may become visible
237 * or perish
238 * @param[in] flags if you want to check whether the edict may also perish from
239 * other players view, you should use the @c VT_PERISHCHK bits
240 * @note If the edict is already visible and flags doesn't contain the
241 * bits of @c VT_PERISHCHK, no further checks are performed - only the
242 * @c VS_YES bits are returned
243 * @return VS_CHANGE is added to the bit mask if the edict flipped its visibility
244 * (invisible to visible or vice versa) VS_YES means the edict is visible for the
245 * given team
246 */
G_TestVis(const int team,Edict * check,const vischeckflags_t flags)247 int G_TestVis (const int team, Edict* check, const vischeckflags_t flags)
248 {
249 Edict* from = nullptr;
250 /* store old flag */
251 const int old = G_IsVisibleForTeam(check, team) ? VS_CHANGE : 0;
252
253 if (g_aidebug->integer)
254 return VS_YES | !old;
255
256 if (!(flags & VT_PERISHCHK) && old)
257 return VS_YES;
258
259 /* test if check is visible */
260 while ((from = G_EdictsGetNextInUse(from)))
261 if (G_Vis(team, from, check, flags))
262 /* visible */
263 return VS_YES | !old;
264
265 /* just return the old state */
266 return old;
267 }
268
G_VisShouldStop(const Edict * ent)269 static bool G_VisShouldStop (const Edict* ent)
270 {
271 return G_IsLivingActor(ent) && !G_IsCivilian(ent);
272 }
273
274 /**
275 * @param[in] team The team looking at the edict (or not)
276 * @param[in] check The edict to check the visibility for
277 * @param[in] visFlags The flags for the vis check
278 * @param[in] playerMask The mask for the players to send the appear/perish events to. If this is @c 0 the events
279 * are not sent - we only update the visflags of the edict
280 * @param[in] ent The edict that was responsible for letting the check edict appear and is looking
281 */
G_DoTestVis(const int team,Edict * check,const vischeckflags_t visFlags,playermask_t playerMask,const Edict * ent)282 static int G_DoTestVis (const int team, Edict* check, const vischeckflags_t visFlags, playermask_t playerMask, const Edict* ent)
283 {
284 int status = 0;
285 const int vis = G_TestVis(team, check, visFlags);
286
287 /* visibility has changed ... */
288 if (vis & VS_CHANGE) {
289 /* swap the vis mask for the given team */
290 const bool appear = (vis & VS_YES) == VS_YES;
291 if (playerMask == 0) {
292 G_VisFlagsSwap(*check, G_TeamToVisMask(team));
293 } else {
294 G_AppearPerishEvent(playerMask, appear, *check, ent);
295 }
296
297 /* ... to visible */
298 if (vis & VS_YES) {
299 status |= VIS_APPEAR;
300 if (G_VisShouldStop(check))
301 status |= VIS_STOP;
302 } else {
303 status |= VIS_PERISH;
304 }
305 } else if (vis == 0 && (visFlags & VT_NEW)) {
306 if (G_IsActor(check)) {
307 G_EventActorAdd(playerMask, *check);
308 }
309 }
310 return status;
311 }
312
313 /**
314 * @brief Sets visible edict on player spawn
315 * @sa G_ClientStartMatch
316 * @sa G_CheckVisTeam
317 * @sa G_AppearPerishEvent
318 */
G_CheckVisPlayer(Player & player,const vischeckflags_t visFlags)319 void G_CheckVisPlayer (Player &player, const vischeckflags_t visFlags)
320 {
321 Edict* ent = nullptr;
322
323 /* check visibility */
324 while ((ent = G_EdictsGetNextInUse(ent))) {
325 /* check if he's visible */
326 G_DoTestVis(player.getTeam(), ent, visFlags, G_PlayerToPM(player), nullptr);
327 }
328 }
329
330 /**
331 * @brief Checks whether an edict appear/perishes for a specific team - also
332 * updates the visflags each edict carries
333 * @sa G_TestVis
334 * @param[in] team Team to check the vis for
335 * @param[in] check The edict that you want to check (and which maybe will appear
336 * or perish for the given team). If this is nullptr every edict will be checked.
337 * @param visFlags Modifiers for the checks
338 * @param[in] ent The edict that is (maybe) seeing other edicts
339 * @return If an actor get visible who's no civilian VIS_STOP is added to the
340 * bit mask, VIS_APPEAR means, that the actor is becoming visible for the queried
341 * team, VIS_PERISH means that the actor (the edict) is getting invisible
342 * @note the edict may not only be actors, but also items of course
343 * @sa G_TeamToPM
344 * @sa G_TestVis
345 * @sa G_AppearPerishEvent
346 * @sa G_CheckVisPlayer
347 * @sa G_CheckVisTeamAll
348 * @note If something appears, the needed information for those clients that are affected
349 * are also send in @c G_AppearPerishEvent
350 */
G_CheckVisTeam(const int team,Edict * check,const vischeckflags_t visFlags,const Edict * ent)351 static int G_CheckVisTeam (const int team, Edict* check, const vischeckflags_t visFlags, const Edict* ent)
352 {
353 int status = 0;
354
355 /* check visibility */
356 if (check->inuse) {
357 /* check if he's visible */
358 status |= G_DoTestVis(team, check, visFlags, G_TeamToPM(team), ent);
359 }
360
361 return status;
362 }
363
364 /**
365 * @brief Do @c G_CheckVisTeam for all entities
366 * ent is the one that is looking at the others
367 */
G_CheckVisTeamAll(const int team,const vischeckflags_t visFlags,const Edict * ent)368 int G_CheckVisTeamAll (const int team, const vischeckflags_t visFlags, const Edict* ent)
369 {
370 Edict* chk = nullptr;
371 int status = 0;
372
373 while ((chk = G_EdictsGetNextInUse(chk))) {
374 status |= G_CheckVisTeam(team, chk, visFlags, ent);
375 }
376 return status;
377 }
378
379 /**
380 * @brief Make everything visible to anyone who can't already see it
381 */
G_VisMakeEverythingVisible(void)382 void G_VisMakeEverythingVisible (void)
383 {
384 Edict* ent = nullptr;
385 while ((ent = G_EdictsGetNextInUse(ent))) {
386 const int playerMask = G_VisToPM(ent->visflags);
387 G_AppearPerishEvent(~playerMask, true, *ent, nullptr);
388 if (G_IsActor(ent))
389 G_SendInventory(~G_TeamToPM(ent->team), *ent);
390 }
391 }
392
393 /**
394 * @brief Check if the edict appears/perishes for the other teams. If they appear
395 * for other teams, the needed information for those clients are also send in
396 * @c G_CheckVisTeam resp. @c G_AppearPerishEvent
397 * @param[in] check The edict that is maybe seen by others. If nullptr, all edicts are checked
398 * @param visFlags Modifiers for the checks
399 * @sa G_CheckVisTeam
400 */
G_CheckVis(Edict * check,const vischeckflags_t visFlags)401 void G_CheckVis (Edict* check, const vischeckflags_t visFlags)
402 {
403 int team;
404
405 for (team = 0; team < MAX_TEAMS; team++)
406 if (level.num_alive[team]) {
407 if (!check) /* no special entity given, so check them all */
408 G_CheckVisTeamAll(team, visFlags, nullptr);
409 else
410 G_CheckVisTeam(team, check, visFlags, nullptr);
411 }
412 }
413
414 /**
415 * @brief Reset the visflags for all edicts in the global list for the
416 * given team - and only for the given team
417 */
G_VisFlagsClear(int team)418 void G_VisFlagsClear (int team)
419 {
420 Edict* ent = nullptr;
421 const teammask_t teamMask = ~G_TeamToVisMask(team);
422 while ((ent = G_EdictsGetNextInUse(ent))) {
423 ent->visflags &= teamMask;
424 }
425 }
426
G_VisFlagsAdd(Edict & ent,teammask_t teamMask)427 void G_VisFlagsAdd (Edict &ent, teammask_t teamMask)
428 {
429 ent.visflags |= teamMask;
430 }
431
G_VisFlagsReset(Edict & ent)432 void G_VisFlagsReset (Edict &ent)
433 {
434 ent.visflags = 0;
435 }
436
G_VisFlagsSwap(Edict & ent,teammask_t teamMask)437 void G_VisFlagsSwap (Edict &ent, teammask_t teamMask)
438 {
439 ent.visflags ^= teamMask;
440 }
441