/** * @brief Test if point is "visible" from team. * @param[in] team A team to test. * @param[in] point A point to check. * @return true if point is "visible" */ static bool G_TeamPointVis (int team, const vec3_t point) { Edict *from = nullptr; vec3_t eye; /* test if point is visible from team */ while ((from = G_EdictsGetNextLivingActorOfTeam(from, team))) { if (G_FrustumVis(from, point)) { /* get viewers eye height */ G_ActorGetEyeVector(from, eye); /* line of sight */ if (!G_TestLine(eye, point)) { const float distance = VectorDist(from->origin, point); bool blocked = false; /* check visibility in the smoke */ if (distance >= UNIT_SIZE) { Edict *e = nullptr; while ((e = G_EdictsGetNextInUse(e))) { if (G_IsSmoke(e) && RayIntersectAABB(eye, point, e->absmin, e->absmax)) { blocked = true; break; } } } if (!blocked) return true; } } } /* not visible */ return false; }
/** * @brief test if check is visible by from * @param[in] team Living team members are always visible. If this is a negative * number we inverse the team rules (see comments included). In combination with VT_NOFRUSTUM * we can check whether there is any edict (that is no in our team) that can see @c check * @param[in] from is from team @c team and must be a living actor * @param[in] check The edict we want to get the visibility for * @param[in] flags @c VT_NOFRUSTUM, ... */ bool G_Vis (const int team, const Edict* from, const Edict* check, const vischeckflags_t flags) { vec3_t eye; /* if any of them isn't in use, then they're not visible */ if (!from->inuse || !check->inuse) return false; /* only actors and 2x2 units can see anything */ if (!G_IsLivingActor(from) && !G_IsActiveCamera(from)) return false; /* living team members are always visible */ if (team >= 0 && check->getTeam() == team && !G_IsDead(check)) return true; /* standard team rules */ if (team >= 0 && from->getTeam() != team) return false; /* inverse team rules */ if (team < 0 && check->getTeam() == -team) return false; /* check for same pos */ if (VectorCompare(from->pos, check->pos)) return true; if (!G_IsVisibleOnBattlefield(check)) return false; /* view distance check */ const int spotDist = G_VisCheckDist(from); if (VectorDistSqr(from->origin, check->origin) > spotDist * spotDist) return false; /* view frustum check */ if (!(flags & VT_NOFRUSTUM) && !G_FrustumVis(from, check->origin)) return false; /* get viewers eye height */ G_ActorGetEyeVector(from, eye); /* line trace check */ switch (check->type) { case ET_ACTOR: case ET_ACTOR2x2: return G_ActorVis(eye, from, check, false) > ACTOR_VIS_0; case ET_ITEM: case ET_CAMERA: case ET_PARTICLE: return !G_LineVis(eye, check->origin); default: return false; } }
/** * @brief calculate how much check is "visible" from @c from * @param[in] from The world coordinate to check from * @param[in] ent The source edict of the check * @param[in] check The edict to check how good (or if at all) it is visible * @param[in] full Perform a full check in different directions. If this is * @c false the actor is fully visible if one vis check returned @c true. With * @c true this function can also return a value != 0.0 and != 1.0. Try to only * use @c true if you really need the full check. Full checks are of course * more expensive. * @return a value between 0.0 and 1.0 which reflects the visibility from 0 * to 100 percent * @note This call isn't cheap - try to do this only if you really need the * visibility check or the statement whether one particular actor see another * particular actor. * @sa CL_ActorVis */ float G_ActorVis (const vec3_t from, const edict_t *ent, const edict_t *check, bool full) { vec3_t test, dir; float delta; int i, n; const float distance = VectorDist(check->origin, ent->origin); /* units that are very close are visible in the smoke */ if (distance > UNIT_SIZE * 1.5f) { vec3_t eyeEnt; edict_t *e = NULL; G_ActorGetEyeVector(ent, eyeEnt); while ((e = G_EdictsGetNext(e))) { if (G_IsSmoke(e)) { if (RayIntersectAABB(eyeEnt, check->absmin, e->absmin, e->absmax) || RayIntersectAABB(eyeEnt, check->absmax, e->absmin, e->absmax)) { return ACTOR_VIS_0; } } } } /* start on eye height */ VectorCopy(check->origin, test); if (G_IsDead(check)) { test[2] += PLAYER_DEAD; delta = 0; } else if (G_IsCrouched(check)) { test[2] += PLAYER_CROUCH - 2; delta = (PLAYER_CROUCH - PLAYER_MIN) / 2 - 2; } else { test[2] += PLAYER_STAND; delta = (PLAYER_STAND - PLAYER_MIN) / 2 - 2; } /* side shifting -> better checks */ dir[0] = from[1] - check->origin[1]; dir[1] = check->origin[0] - from[0]; dir[2] = 0; VectorNormalizeFast(dir); VectorMA(test, -7, dir, test); /* do 3 tests */ n = 0; for (i = 0; i < 3; i++) { if (!G_LineVis(from, test)) { if (full) n++; else return ACTOR_VIS_100; } /* look further down or stop */ if (!delta) { if (n > 0) return ACTOR_VIS_100; else return ACTOR_VIS_0; } VectorMA(test, 7, dir, test); test[2] -= delta; } /* return factor */ switch (n) { case 0: return ACTOR_VIS_0; case 1: return ACTOR_VIS_10; case 2: return ACTOR_VIS_50; default: return ACTOR_VIS_100; } }
/** * @brief Deals splash damage to a target and its surroundings. * @param[in] ent The shooting actor * @param[in] fd The fire definition that defines what type of damage is dealt and how big the splash radius is. * @param[in] impact The impact vector where the grenade is exploding * @param[in,out] mock pseudo shooting - only for calculating mock values - nullptr for real shots * @param[in] tr The trace where the grenade hits something (or not) */ static void G_SplashDamage (Actor* ent, const fireDef_t* fd, vec3_t impact, shot_mock_t* mock, const trace_t* tr) { assert(fd->splrad > 0.0f); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); Edict* check = nullptr; while ((check = G_EdictsGetNextInUse(check))) { /* If we use a blinding weapon we skip the target if it's looking * away from the impact location. */ if (shock && !G_FrustumVis(check, impact)) continue; const bool isActor = G_IsLivingActor(check); vec3_t center; if (G_IsBrushModel(check) && G_IsBreakable(check)) check->absBox.getCenter(center); else if (isActor || G_IsBreakable(check)) VectorCopy(check->origin, center); else continue; /* check for distance */ float dist = VectorDist(impact, center); dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0.0f; if (dist > fd->splrad) continue; if (fd->irgoggles) { if (isActor) { /* check whether this actor (check) is in the field of view of the 'shooter' (ent) */ if (G_FrustumVis(ent, check->origin)) { if (!mock) { vec3_t eyeEnt; G_ActorGetEyeVector(ent, eyeEnt); if (!G_SmokeVis(eyeEnt, check)) { const unsigned int playerMask = G_TeamToPM(ent->getTeam()) ^ G_VisToPM(check->visflags); G_AppearPerishEvent(playerMask, true, *check, ent); G_VisFlagsAdd(*check, G_PMToVis(playerMask)); } } } } continue; } /* check for walls */ if (isActor && G_TestLine(impact, check->origin)) continue; /* do damage */ const int damage = shock ? 0 : fd->spldmg[0] * (1.0f - dist / fd->splrad); if (mock) mock->allow_self = true; /* Send hurt sounds for actors, but only if they'll recieve damage from this attack */ if (G_Damage(check, fd, damage, ent, mock, nullptr) && isActor && (G_ApplyProtection(check, fd->dmgweight, damage) > 0) && !shock) { const teamDef_t* teamDef = check->chr.teamDef; const int gender = check->chr.gender; const char* sound = teamDef->getActorSound(gender, SND_HURT); G_EventSpawnSound(G_VisToPM(check->visflags), *check, nullptr, sound); } if (mock) mock->allow_self = false; } /** @todo splash might also hit other surfaces and the trace doesn't handle that */ if (tr && G_FireAffectedSurface(tr->surface, fd)) { /* move a little away from the impact vector */ VectorMA(impact, 1, tr->plane.normal, impact); G_SpawnParticle(impact, tr->contentFlags >> 8, "burning"); }
/** * @brief calculate how much check is "visible" by @c ent * @param[in] ent The source edict of the check * @param[in] check The edict to check how good (or if at all) it is visible * @param[in] full Perform a full check in different directions. If this is * @c false the actor is fully visible if one vis check returned @c true. With * @c true this function can also return a value != 0.0 and != 1.0. Try to only * use @c true if you really need the full check. Full checks are of course * more expensive. * @return a value between 0.0 and 1.0 (one of the ACTOR_VIS_* constants) which * reflects the visibility from 0 to 100 percent * @note This call isn't cheap - try to do this only if you really need the * visibility check or the statement whether one particular actor see another * particular actor. * @sa CL_ActorVis */ float G_ActorVis (const Edict* ent, const Edict* check, bool full) { vec3_t eyeEnt; G_ActorGetEyeVector(ent, eyeEnt); if (G_SmokeVis(eyeEnt, check)) return ACTOR_VIS_0; vec3_t test, dir; float delta; /* start on eye height */ VectorCopy(check->origin, test); if (G_IsDead(check)) { test[2] += PLAYER_DEAD; delta = 0; } else if (G_IsCrouched(check)) { test[2] += PLAYER_CROUCH - 2; delta = (PLAYER_CROUCH - PLAYER_MIN) / 2 - 2; } else { test[2] += PLAYER_STAND; delta = (PLAYER_STAND - PLAYER_MIN) / 2 - 2; } /* side shifting -> better checks */ dir[0] = eyeEnt[1] - check->origin[1]; dir[1] = check->origin[0] - eyeEnt[0]; dir[2] = 0; VectorNormalizeFast(dir); VectorMA(test, -7, dir, test); /* do 3 tests */ int n = 0; for (int i = 0; i < 3; i++) { if (!G_LineVis(eyeEnt, test)) { if (full) n++; else return ACTOR_VIS_100; } /* look further down or stop */ if (!delta) { if (n > 0) return ACTOR_VIS_100; else return ACTOR_VIS_0; } VectorMA(test, 7, dir, test); test[2] -= delta; } /* return factor */ switch (n) { case 0: return ACTOR_VIS_0; case 1: return ACTOR_VIS_10; case 2: return ACTOR_VIS_50; default: return ACTOR_VIS_100; } }