/** * @brief Get the weapon firing TUs of the item in the right hand of the edict. * @return -1 if no firedef was found for the item or the reaction fire mode is not activated for the right hand. * @todo why only right hand? * @param[in] ent The reaction firing actor * @param[in] target The target to check reaction fire for (e.g. check whether the weapon that was marked for * using in reaction fire situations can handle the distance between the shooter and the target) * @param[in] invList The items that are checked for reaction fire * @note This does 'not' return the weapon (lowest TU costs, highest damage, highest accuracy) but the first weapon that * would fit for reaction fire. */ static int G_ReactionFireGetTUsForItem (const edict_t *ent, const edict_t *target, const invList_t *invList) { if (invList && invList->item.m && invList->item.t->weapon && (!invList->item.t->reload || invList->item.a > 0)) { const fireDef_t *fdArray = FIRESH_FiredefForWeapon(&invList->item); const chrFiremodeSettings_t *fmSetting; if (fdArray == NULL) return -1; fmSetting = &ent->chr.RFmode; if (fmSetting->hand == ACTOR_HAND_RIGHT && fmSetting->fmIdx >= 0 && fmSetting->fmIdx < MAX_FIREDEFS_PER_WEAPON) { /* If a RIGHT-hand firemode is selected and sane. */ const fireDefIndex_t fmIdx = fmSetting->fmIdx; const int reactionFire = G_PLAYER_FROM_ENT(ent)->reactionLeftover; const fireDef_t *fd = &fdArray[fmIdx]; const int tus = fd->time + reactionFire; if (tus <= ent->TU && fd->range > VectorDist(ent->origin, target->origin)) { return tus; } } } return -1; }
/** * @brief Spawns a smoke field that is available for some rounds * @param[in] vec The position in the world that is the center of the smoke field * @param[in] particle The id of the particle (see ptl_*.ufo script files in base/ufos) * @param[in] rounds The number of rounds the particle will last * @todo Does '2 rounds' mean: created in player's turn, last through the aliens turn, vanish before the 2nd player's turn ?? * @param[in] radius The max distance of a cell from the center to get a particle */ void G_SpawnSmokeField (const vec3_t vec, const char *particle, int rounds, vec_t radius) { vec_t x, y; G_SpawnSmoke(vec, particle, rounds); /* for all cells in a square of +/- radius */ for (x = vec[0] - radius; x <= vec[0] + radius; x += UNIT_SIZE) { for (y = vec[1] - radius; y <= vec[1] + radius; y += UNIT_SIZE) { vec3_t end; trace_t tr; VectorSet(end, x, y, vec[2]); /* cut off the edges of the square to resemble a circle */ if (VectorDist(end, vec) > radius) continue; tr = G_Trace(vec, end, NULL, MASK_SMOKE_AND_FIRE); /* trace didn't reach the target - something was hit before */ if (tr.fraction < 1.0 || (tr.contentFlags & CONTENTS_WATER)) { continue; } G_SpawnSmoke(end, particle, rounds); } } }
void G_SpawnStunSmokeField (const vec3_t vec, const char *particle, int rounds, int damage, vec_t radius) { vec_t x, y; G_SpawnStunSmoke(vec, particle, rounds, damage); for (x = vec[0] - radius; x <= vec[0] + radius; x += UNIT_SIZE) { for (y = vec[1] - radius; y <= vec[1] + radius; y += UNIT_SIZE) { vec3_t end; trace_t tr; VectorSet(end, x, y, vec[2]); if (VectorDist(end, vec) > radius) continue; tr = G_Trace(vec, end, NULL, MASK_SMOKE_AND_FIRE); /* trace didn't reach the target - something was hit before */ if (tr.fraction < 1.0 || (tr.contentFlags & CONTENTS_WATER)) { continue; } G_SpawnStunSmoke(end, particle, rounds, damage); } } }
/** * @brief Calculates transformation matrix for the model and its tags * @note The transformation matrix is only calculated once */ static float* R_CalcTransform (entity_t* e) { transform_t* t; float* mp; float mt[16], mc[16]; /* check if this entity is already transformed */ t = &e->transform; if (t->processing) Com_Error(ERR_DROP, "Ring in entity transformations!"); if (t->done) return t->matrix; /* process this matrix */ t->processing = true; mp = nullptr; /* do parent object transformations first */ if (e->tagent) { /* tag transformation */ const model_t* model = e->tagent->model; const mAliasTagOrientation_t* current = nullptr; const mAliasTagOrientation_t* old = nullptr; const animState_t* as = &e->tagent->as; R_GetTags(model, e->tagname, as->frame, as->oldframe, ¤t, &old); if (current != nullptr && old != nullptr) { float interpolated[16]; /* parent transformation */ mp = R_CalcTransform(e->tagent); /* do interpolation */ R_InterpolateTransform(as->backlerp, model->alias.num_frames, current, old, interpolated); /* transform */ GLMatrixMultiply(mp, interpolated, mt); mp = mt; } } GLMatrixAssemble(e->origin, e->angles, mc); /* combine transformations */ if (mp) GLMatrixMultiply(mp, mc, t->matrix); else memcpy(t->matrix, mc, sizeof(float) * 16); /* matrix elements 12..14 contain (forward) translation vector, which is also the origin of model after transform */ e->distanceFromViewOrigin = VectorDist(&t->matrix[12], refdef.viewOrigin); /* we're done */ t->done = true; t->processing = false; return t->matrix; }
/** * @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 Get the weapon firing TUs of the item in the right hand of the edict. * @return -1 if no firedef was found for the item or the reaction fire mode is not activated for the right hand. * @todo why only right hand? * @param[in] shooter The reaction firing actor * @param[in] target The target to check reaction fire for (e.g. check whether the weapon that was marked for * using in reaction fire situations can handle the distance between the shooter and the target) * @note This does 'not' return the weapon (lowest TU costs, highest damage, highest accuracy) but the first weapon that * would fit for reaction fire. */ static int G_ReactionFireGetTUsForItem (const Edict *shooter, const Edict *target) { const FiremodeSettings *fmSetting = &shooter->chr.RFmode; const Item *weapon = shooter->getHandItem(fmSetting->getHand()); if (weapon && weapon->ammoDef() && weapon->isWeapon() && !weapon->mustReload()) { const fireDef_t *fdArray = weapon->getFiredefs(); if (fdArray == nullptr) return -1; if (fmSetting->getFmIdx() >= 0 && fmSetting->getFmIdx() < MAX_FIREDEFS_PER_WEAPON) { /* If firemode is sane. */ const fireDefIndex_t fmIdx = fmSetting->getFmIdx(); const int reactionFire = shooter->getPlayer().reactionLeftover; assert(reactionFire == 0); /* check if this is still in use */ const fireDef_t *fd = &fdArray[fmIdx]; const int tus = G_ActorGetModifiedTimeForFiredef(shooter, fd, true) + reactionFire; if (tus <= shooter->TU && fd->range > VectorDist(shooter->origin, target->origin)) { return tus; } } } return -1; }
/** * @brief Decides if following events should be delayed. If the projectile has a speed value assigned, the * delay is relative to the distance the projectile flies. There are other fire definition related options * that might delay the execution of further events. */ int CL_ActorDoShootTime (const eventRegister_t* self, dbuffer* msg, eventTiming_t* eventTiming) { int flags, dummy; int objIdx, surfaceFlags; int weap_fds_idx, fd_idx; shoot_types_t shootType; vec3_t muzzle, impact; int eventTime = eventTiming->shootTime; /* read data */ NET_ReadFormat(msg, self->formatString, &dummy, &dummy, &dummy, &objIdx, &weap_fds_idx, &fd_idx, &shootType, &flags, &surfaceFlags, &muzzle, &impact, &dummy); const objDef_t* obj = INVSH_GetItemByIDX(objIdx); const fireDef_t* fd = FIRESH_GetFiredef(obj, weap_fds_idx, fd_idx); if (!(flags & SF_BOUNCED)) { /* shooting */ if (fd->speed > 0.0 && !CL_OutsideMap(impact, UNIT_SIZE * 10)) { eventTiming->impactTime = eventTiming->shootTime + 1000 * VectorDist(muzzle, impact) / fd->speed; } else { eventTiming->impactTime = eventTiming->shootTime; } if (!cls.isOurRound()) eventTiming->nextTime = CL_GetNextTime(self, eventTiming, eventTiming->impactTime + 1400); else eventTiming->nextTime = CL_GetNextTime(self, eventTiming, eventTiming->impactTime + 400); if (fd->delayBetweenShots > 0.0) eventTiming->shootTime += 1000 / fd->delayBetweenShots; } else { /* only a bounced shot */ eventTime = eventTiming->impactTime; if (fd->speed > 0.0) { eventTiming->impactTime += 1000 * VectorDist(muzzle, impact) / fd->speed; eventTiming->nextTime = CL_GetNextTime(self, eventTiming, eventTiming->impactTime); } } eventTiming->parsedDeath = false; return eventTime; }
/** * @brief Returns distance between AI and target * @note @c target (passed trough the lua stack) The target to which the distance is calculated */ static int AIL_distance (lua_State *L) { vec_t dist; aiActor_t* target; /* check parameter */ assert(lua_gettop(L) && lua_isactor(L, 1)); /* calculate distance */ target = lua_toactor(L, 1); dist = VectorDist(AIL_ent->origin, target->ent->origin); lua_pushnumber(L, dist); return 1; }
/** * @brief tests for smoke interference * @param[in] from The point to check visibility from * @param[in] check The edict to check visibility to * @return true if @c check is invisible from @c from (smoke is in the way), false otherwise. */ bool G_SmokeVis (const vec3_t from, const Edict* check) { const float distance = VectorDist(check->origin, from); /* units that are very close are visible in the smoke */ if (distance > UNIT_SIZE * 1.5f) { Edict* e = nullptr; while ((e = G_EdictsGetNextInUse(e))) { if (G_IsSmoke(e)) { if (RayIntersectAABB(from, check->absBox.mins, e->absBox) || RayIntersectAABB(from, check->absBox.maxs, e->absBox)) { return true; } } } } return false; }
bool ReactionFire::isInWeaponRange (const Actor* shooter, const Edict* target, const fireDef_t* fd) const { assert(fd); return fd->range >= VectorDist(shooter->origin, target->origin); }
/** * @brief Applies morale changes to actors around a wounded or killed actor. * @note only called when mor_panic is not zero * @param[in] type Type of morale modifier (@sa morale_modifiers) * @param[in] victim An actor being a victim of the attack. * @param[in] attacker An actor being attacker in this attack. * @param[in] param Used to modify morale changes, for G_Damage() it is value of damage. * @sa G_Damage */ static void G_Morale (int type, const edict_t * victim, const edict_t * attacker, int param) { edict_t *ent = NULL; int newMorale; float mod; while ((ent = G_EdictsGetNextInUse(ent))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (ent->type == ET_ACTOR && !G_IsDead(ent) && ent->team != TEAM_CIVILIAN) { switch (type) { case ML_WOUND: case ML_DEATH: /* morale damage depends on the damage */ mod = mob_wound->value * param; /* death hurts morale even more than just damage */ if (type == ML_DEATH) mod += mob_death->value; /* seeing how someone gets shot increases the morale change */ if (ent == victim || (G_FrustumVis(ent, victim->origin) && G_ActorVis(ent->origin, ent, victim, false))) mod *= mof_watching->value; if (attacker != NULL && ent->team == attacker->team) { /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */ /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */ if (victim->team == attacker->team) mod *= mof_teamkill->value; else mod *= mof_enemy->value; } /* seeing a civilian die is more "acceptable" */ if (G_IsCivilian(victim)) mod *= mof_civilian->value; /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */ if (victim->team == ent->team || (G_IsCivilian(victim) && ent->team != TEAM_ALIEN && sv_maxclients->integer == 1)) mod *= -1; if (attacker != NULL) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5, VectorDist(ent->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5, VectorDist(ent->origin, victim->origin) / mor_distance->value) * mor_victim->value; } /* morale damage depends on the number of living allies */ mod *= (1 - mon_teamfactor->value) + mon_teamfactor->value * (level.num_spawned[victim->team] + 1) / (level.num_alive[victim->team] + 1); /* being hit isn't fun */ if (ent == victim) mod *= mor_pain->value; break; default: gi.DPrintf("Undefined morale modifier type %i\n", type); mod = 0; break; } /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ newMorale = ent->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(ent->chr.score.skills[ABILITY_MIND])) ent->morale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); else if (newMorale < 0) ent->morale = 0; else ent->morale = newMorale; /* send phys data */ G_SendStats(ent); } } }
/** * @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 - NULL for real shots * @param[in] tr The trace where the grenade hits something (or not) */ static void G_SplashDamage (edict_t *ent, const fireDef_t *fd, vec3_t impact, shot_mock_t *mock, const trace_t* tr) { edict_t *check = NULL; vec3_t center; float dist; int damage; const bool shock = (fd->obj->dmgtype == gi.csi->damShock); assert(fd->splrad > 0.0); 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; if (G_IsBrushModel(check) && G_IsBreakable(check)) VectorCenterFromMinsMaxs(check->absmin, check->absmax, center); else if (G_IsLivingActor(check) || G_IsBreakable(check)) VectorCopy(check->origin, center); else continue; /* check for distance */ dist = VectorDist(impact, center); dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0; if (dist > fd->splrad) continue; if (fd->irgoggles) { if (G_IsActor(check)) { /* check whether this actor (check) is in the field of view of the 'shooter' (ent) */ if (G_FrustumVis(ent, check->origin)) { if (!mock) { const unsigned int playerMask = G_TeamToPM(ent->team) ^ G_VisToPM(check->visflags); G_AppearPerishEvent(playerMask, true, check, ent); G_VisFlagsAdd(check, G_PMToVis(playerMask)); } } } continue; } /* check for walls */ if (G_IsLivingActor(check) && !G_ActorVis(impact, ent, check, false)) continue; /* do damage */ if (shock) damage = 0; else damage = fd->spldmg[0] * (1.0 - dist / fd->splrad); if (mock) mock->allow_self = true; G_Damage(check, fd, damage, ent, mock, NULL); 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 Applies morale changes to actors around a wounded or killed actor. * @note only called when mor_panic is not zero * @param[in] type Type of morale modifier (@sa morale_modifiers) * @param[in] victim An actor being a victim of the attack. * @param[in] attacker An actor being attacker in this attack. * @param[in] param Used to modify morale changes, for G_Damage() it is value of damage. * @sa G_Damage */ static void G_Morale (morale_modifiers type, const Edict* victim, const Edict* attacker, int param) { Actor* actor = nullptr; while ((actor = G_EdictsGetNextLivingActor(actor))) { /* this only applies to ET_ACTOR but not ET_ACTOR2x2 */ if (actor->type != ET_ACTOR) continue; if (G_IsCivilian(actor)) continue; /* morale damage depends on the damage */ float mod = mob_wound->value * param; if (type == ML_SHOOT) mod *= mob_shoot->value; /* death hurts morale even more than just damage */ if (type == ML_DEATH) mod += mob_death->value; /* seeing how someone gets shot increases the morale change */ if (actor == victim || (G_FrustumVis(actor, victim->origin) && G_ActorVis(actor, victim, false))) mod *= mof_watching->value; if (attacker != nullptr && actor->isSameTeamAs(attacker)) { /* teamkills are considered to be bad form, but won't cause an increased morale boost for the enemy */ /* morale boost isn't equal to morale loss (it's lower, but morale gets regenerated) */ if (victim->isSameTeamAs(attacker)) mod *= mof_teamkill->value; else mod *= mof_enemy->value; } /* seeing a civilian die is more "acceptable" */ if (G_IsCivilian(victim)) mod *= mof_civilian->value; /* if an ally (or in singleplayermode, as human, a civilian) got shot, lower the morale, don't heighten it. */ if (victim->isSameTeamAs(actor) || (G_IsCivilian(victim) && !G_IsAlien(actor) && G_IsSinglePlayer())) mod *= -1; if (attacker != nullptr) { /* if you stand near to the attacker or the victim, the morale change is higher. */ mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value + pow(0.5f, VectorDist(actor->origin, attacker->origin) / mor_distance->value) * mor_attacker->value; } else { mod *= mor_default->value + pow(0.5f, VectorDist(actor->origin, victim->origin) / mor_distance->value) * mor_victim->value; } /* morale damage depends on the number of living allies */ mod *= (1 - mon_teamfactor->value) + mon_teamfactor->value * (level.num_spawned[victim->getTeam()] + 1) / (level.num_alive[victim->getTeam()] + 1); /* being hit isn't fun */ if (actor == victim) mod *= mor_pain->value; /* clamp new morale */ /*+0.9 to allow weapons like flamethrowers to inflict panic (typecast rounding) */ const int newMorale = actor->morale + (int) (MORALE_RANDOM(mod) + 0.9); if (newMorale > GET_MORALE(actor->chr.score.skills[ABILITY_MIND])) actor->setMorale(GET_MORALE(actor->chr.score.skills[ABILITY_MIND])); else if (newMorale < 0) actor->setMorale(0); else actor->setMorale(newMorale); /* send phys data */ G_SendStats(*actor); } }
/** * @brief Update the camera position. This can be done in two different reasons. The first is the user input, the second * is an active camera route. The camera route overrides the user input and is lerping the movement until the final position * is reached. */ void CL_CameraMove (void) { float frac; vec3_t delta; int i; /* get relevant variables */ const float rotspeed = (cl_camrotspeed->value > MIN_CAMROT_SPEED) ? ((cl_camrotspeed->value < MAX_CAMROT_SPEED) ? cl_camrotspeed->value : MAX_CAMROT_SPEED) : MIN_CAMROT_SPEED; const float movespeed = (cl_cammovespeed->value > MIN_CAMMOVE_SPEED) ? ((cl_cammovespeed->value < MAX_CAMMOVE_SPEED) ? cl_cammovespeed->value / cl.cam.zoom : MAX_CAMMOVE_SPEED / cl.cam.zoom) : MIN_CAMMOVE_SPEED / cl.cam.zoom; const float moveaccel = (cl_cammoveaccel->value > MIN_CAMMOVE_ACCEL) ? ((cl_cammoveaccel->value < MAX_CAMMOVE_ACCEL) ? cl_cammoveaccel->value / cl.cam.zoom : MAX_CAMMOVE_ACCEL / cl.cam.zoom) : MIN_CAMMOVE_ACCEL / cl.cam.zoom; if (cls.state != ca_active) return; if (!viddef.viewWidth || !viddef.viewHeight) return; /* calculate camera omega */ /* stop acceleration */ frac = cls.frametime * moveaccel * 2.5; for (i = 0; i < 2; i++) { if (fabs(cl.cam.omega[i]) > frac) { if (cl.cam.omega[i] > 0) cl.cam.omega[i] -= frac; else cl.cam.omega[i] += frac; } else cl.cam.omega[i] = 0; /* rotational acceleration */ if (i == YAW) cl.cam.omega[i] += CL_GetKeyMouseState(STATE_ROT) * frac * 2; else cl.cam.omega[i] += CL_GetKeyMouseState(STATE_TILT) * frac * 2; if (cl.cam.omega[i] > rotspeed) cl.cam.omega[i] = rotspeed; if (-cl.cam.omega[i] > rotspeed) cl.cam.omega[i] = -rotspeed; } cl.cam.omega[ROLL] = 0; /* calculate new camera angles for this frame */ VectorMA(cl.cam.angles, cls.frametime, cl.cam.omega, cl.cam.angles); if (cl.cam.angles[PITCH] > cl_campitchmax->value) cl.cam.angles[PITCH] = cl_campitchmax->value; if (cl.cam.angles[PITCH] < cl_campitchmin->value) cl.cam.angles[PITCH] = cl_campitchmin->value; AngleVectors(cl.cam.angles, cl.cam.axis[0], cl.cam.axis[1], cl.cam.axis[2]); /* camera route overrides user input */ if (cameraRoute) { /* camera route */ frac = cls.frametime * moveaccel * 2; if (VectorDist(cl.cam.origin, routeFrom) > routeDist - 200) { VectorMA(cl.cam.speed, -frac, routeDelta, cl.cam.speed); VectorNormalize2(cl.cam.speed, delta); if (DotProduct(delta, routeDelta) < 0.05) { cameraRoute = false; CL_BlockBattlescapeEvents(false); } } else VectorMA(cl.cam.speed, frac, routeDelta, cl.cam.speed); } else { /* normal camera movement */ /* calculate ground-based movement vectors */ const float angle = cl.cam.angles[YAW] * torad; const float sy = sin(angle); const float cy = cos(angle); vec3_t g_forward, g_right; VectorSet(g_forward, cy, sy, 0.0); VectorSet(g_right, sy, -cy, 0.0); /* calculate camera speed */ /* stop acceleration */ frac = cls.frametime * moveaccel; if (VectorLength(cl.cam.speed) > frac) { VectorNormalize2(cl.cam.speed, delta); VectorMA(cl.cam.speed, -frac, delta, cl.cam.speed); } else VectorClear(cl.cam.speed); /* acceleration */ frac = cls.frametime * moveaccel * 3.5; VectorClear(delta); VectorScale(g_forward, CL_GetKeyMouseState(STATE_FORWARD), delta); VectorMA(delta, CL_GetKeyMouseState(STATE_RIGHT), g_right, delta); VectorNormalize(delta); VectorMA(cl.cam.speed, frac, delta, cl.cam.speed); /* lerp the level change */ if (cl.cam.lerplevel < cl_worldlevel->value) { cl.cam.lerplevel += LEVEL_SPEED * (cl_worldlevel->value - cl.cam.lerplevel + LEVEL_MIN) * cls.frametime; if (cl.cam.lerplevel > cl_worldlevel->value) cl.cam.lerplevel = cl_worldlevel->value; } else if (cl.cam.lerplevel > cl_worldlevel->value) { cl.cam.lerplevel -= LEVEL_SPEED * (cl.cam.lerplevel - cl_worldlevel->value + LEVEL_MIN) * cls.frametime; if (cl.cam.lerplevel < cl_worldlevel->value) cl.cam.lerplevel = cl_worldlevel->value; } } /* clamp speed */ frac = VectorLength(cl.cam.speed) / movespeed; if (frac > 1.0) VectorScale(cl.cam.speed, 1.0 / frac, cl.cam.speed); /* zoom change */ frac = CL_GetKeyMouseState(STATE_ZOOM); if (frac > 0.1) { cl.cam.zoom *= 1.0 + cls.frametime * cl_camzoomspeed->value * frac; /* ensure zoom isn't greater than either MAX_ZOOM or cl_camzoommax */ cl.cam.zoom = std::min(std::min(MAX_ZOOM, cl_camzoommax->value), cl.cam.zoom); } else if (frac < -0.1) { cl.cam.zoom /= 1.0 - cls.frametime * cl_camzoomspeed->value * frac; /* ensure zoom isn't less than either MIN_ZOOM or cl_camzoommin */ cl.cam.zoom = std::max(std::max(MIN_ZOOM, cl_camzoommin->value), cl.cam.zoom); } CL_ViewCalcFieldOfViewX(); /* calc new camera reference and new camera real origin */ VectorMA(cl.cam.origin, cls.frametime, cl.cam.speed, cl.cam.origin); cl.cam.origin[2] = 0.; if (cl_isometric->integer) { CL_ClampCamToMap(72.); VectorMA(cl.cam.origin, -CAMERA_START_DIST + cl.cam.lerplevel * CAMERA_LEVEL_HEIGHT, cl.cam.axis[0], cl.cam.camorg); cl.cam.camorg[2] += CAMERA_START_HEIGHT + cl.cam.lerplevel * CAMERA_LEVEL_HEIGHT; } else { const double border = 144.0 * (cl.cam.zoom - cl_camzoommin->value - 0.4) / cl_camzoommax->value; CL_ClampCamToMap(std::min(border, 86.0)); VectorMA(cl.cam.origin, -CAMERA_START_DIST / cl.cam.zoom , cl.cam.axis[0], cl.cam.camorg); cl.cam.camorg[2] += CAMERA_START_HEIGHT / cl.cam.zoom + cl.cam.lerplevel * CAMERA_LEVEL_HEIGHT; } }
/** @todo bad implementation -- copying light pointers every frame is a very wrong idea * @brief Recalculate active lights list for the given entity; R_CalcTransform(ent) should be called before this * @note to accelerate math, the diagonal of aabb is used to approximate max distance from entity's origin to its most distant point * while this is a gross exaggeration for many models, the sole purpose of it is to be used for filtering out distant lights, * so nothing is broken by it. * @param[in] ent Entity to recalculate lights for * @sa R_AddLightToEntity */ void R_UpdateLightList (entity_t *ent) { int i; vec_t *pos; /**< Worldspace position for which lighting is calculated */ entity_t *rootEnt; /**< The root entitity of tagent tree, which holds the lighting data (af any) */ lighting_t *ltng; /**< Lighting data for the entity being processed */ vec3_t diametralVec; /** < conservative estimate of entity's bounding sphere diameter, in vector form */ float diameter; /** < value of this entity's diameter (approx) */ vec3_t fakeSunPos; /**< as if sun wasn't at infinite distance */ bool cached = false; /* Find the root of tagent tree which actually owns the lighting data; it is assumed that there is no loops, * since R_CalcTransform calls Com_Error on those */ for (rootEnt = ent; rootEnt->tagent; rootEnt = ent->tagent) ; ltng = ent->lighting = rootEnt->lighting; if (!ltng) { /* Entity got no lighting data, so substitute defaults (no dynamic lights, but exposed to sunlight) */ /** @todo Replace this hack with something more legit (hack can cause bizarre stencil shadows if enabled) */ OBJZERO(fakeLightingData); ent->lighting = &fakeLightingData; return; } if (ltng->lastLitFrame == r_locals.frame) return; /* nothing to do, already calculated lighting for this frame */ ltng->lastLitFrame = r_locals.frame; /* we have to use the offset from (accumulated) transform matrix, because entity origin is not necessarily the point where model is acually rendered */ pos = ent->transform.matrix + 12; /* type system hack, sorry */ VectorSubtract(ent->maxs, ent->mins, diametralVec); /** @todo what if origin is NOT inside aabb? then this estimate will not be conservative enough */ diameter = VectorLength(diametralVec); /** @todo clear caches when r_dynamic_lights cvar is changed OR always keep maximal # of static lights in the cache */ if (VectorDist(pos, ltng->lastCachePos) < CACHE_CLEAR_TRESHOLD) { cached = true; } else { ltng->numLights = 0; /* clear the list of lights */ ltng->numCachedLights = 0; VectorCopy(pos, ltng->lastCachePos); } /* Check if origin of this entity is hit by sunlight (not the best test, but at least fast) */ if (!cached) { if (ent->flags & RF_ACTOR) { /** @todo Hack to avoid dropships being shadowed by lightclips placed at them. Should be removed once correct global illumination model is done */ VectorMA(pos, 8192.0, refdef.sunVector, fakeSunPos); R_Trace(pos, fakeSunPos, 0, MASK_SOLID); ltng->inShadow = refdef.trace.fraction != 1.0; } else { ltng->inShadow = false; } } if (!r_dynamic_lights->integer) return; if (!cached) { /* Rebuild list of static lights */ for (i = 0; i < refdef.numStaticLights; i++) { light_t *light = &refdef.staticLights[i]; const float distSqr = VectorDistSqr(pos, light->origin); if (distSqr > (diameter + light->radius) * (diameter + light->radius)) continue; R_Trace(pos, light->origin, 0, MASK_SOLID); if (refdef.trace.fraction == 1.0) R_AddLightToEntity(pos, ltng, light, distSqr); } /* Save static lights to cache */ for (i = 0; i < ltng->numLights; i++) ltng->cachedLights[i] = ltng->lights[i]; ltng->numCachedLights = ltng->numLights; } else { /* Copy static lights from cache */ for (i = 0; i < ltng->numCachedLights; i++) ltng->lights[i] = ltng->cachedLights[i]; ltng->numLights = ltng->numCachedLights; } /* add dynamic lights, too */ for (i = 0; i < refdef.numDynamicLights; i++) { light_t *light = &refdef.dynamicLights[i]; const float distSqr = VectorDistSqr(pos, light->origin); if (distSqr > (diameter + light->radius) * (diameter + light->radius)) continue; R_Trace(pos, light->origin, 0, MASK_SOLID); if (refdef.trace.fraction == 1.0) R_AddLightToEntity(pos, ltng, light, distSqr); } }
/** * @brief Calculates the time the event should get executed. If two events return the same time, * they are going to be executed in the order the were parsed. * @param[in] eType The event type * @param[in,out] msg The message buffer that can be modified to get the event time * @param[in] dt Delta time in msec since the last event was parsed */ int CL_GetEventTime (const event_t eType, struct dbuffer *msg, const int dt) { const eventRegister_t *eventData = CL_GetEvent(eType); #ifdef OLDEVENTTIME /* the time the event should be executed. This value is used to sort the * event chain to determine which event must be executed at first. This * value also ensures, that the events are executed in the correct * order. E.g. @c impactTime is used to delay some events in case the * projectile needs some time to reach its target. */ int eventTime; if (eType == EV_RESET) { parsedDeath = qfalse; nextTime = 0; shootTime = 0; impactTime = 0; } else if (eType == EV_ACTOR_DIE) parsedDeath = qtrue; /* get event time */ if (nextTime < cl.time) nextTime = cl.time; if (impactTime < cl.time) impactTime = cl.time; if (eType == EV_ACTOR_DIE || eType == EV_MODEL_EXPLODE) eventTime = impactTime; else if (eType == EV_ACTOR_SHOOT || eType == EV_ACTOR_SHOOT_HIDDEN) eventTime = shootTime; else if (eType == EV_RESULTS) eventTime = nextTime + 1400; else eventTime = nextTime; if (eType == EV_ENT_APPEAR || eType == EV_INV_ADD || eType == EV_PARTICLE_APPEAR || eType == EV_PARTICLE_SPAWN) { if (parsedDeath) { /* drop items after death (caused by impact) */ eventTime = impactTime + 400; /* EV_INV_ADD messages are the last events sent after a death */ if (eType == EV_INV_ADD) parsedDeath = qfalse; } else if (impactTime > cl.time) { /* item thrown on the ground */ eventTime = impactTime + 75; } } /* calculate time interval before the next event */ switch (eType) { case EV_ACTOR_APPEAR: if (cl.actTeam != cls.team) nextTime += 600; break; case EV_INV_RELOAD: /* let the reload sound play */ nextTime += 600; break; case EV_ACTOR_START_SHOOT: nextTime += 300; shootTime = nextTime; break; case EV_ACTOR_SHOOT_HIDDEN: { int first; int objIdx; const objDef_t *obj; weaponFireDefIndex_t weapFdsIdx; fireDefIndex_t fireDefIndex; NET_ReadFormat(msg, eventData->formatString, &first, &objIdx, &weapFdsIdx, &fireDefIndex); obj = INVSH_GetItemByIDX(objIdx); if (first) { nextTime += 500; impactTime = shootTime = nextTime; } else { const fireDef_t *fd = FIRESH_GetFiredef(obj, weapFdsIdx, fireDefIndex); /* impact right away - we don't see it at all * bouncing is not needed here, too (we still don't see it) */ impactTime = shootTime; nextTime = shootTime + 1400; if (fd->delayBetweenShots > 0.0) shootTime += 1000 / fd->delayBetweenShots; } parsedDeath = qfalse; } break; case EV_ACTOR_MOVE: { le_t *le; int number, i; int time = 0; int pathLength; byte crouchingState; pos3_t pos, oldPos; number = NET_ReadShort(msg); /* get le */ le = LE_Get(number); if (!le) LE_NotFoundError(number); pathLength = NET_ReadByte(msg); /* Also skip the final position */ NET_ReadByte(msg); NET_ReadByte(msg); NET_ReadByte(msg); VectorCopy(le->pos, pos); crouchingState = LE_IsCrouched(le) ? 1 : 0; for (i = 0; i < pathLength; i++) { const dvec_t dvec = NET_ReadShort(msg); const byte dir = getDVdir(dvec); VectorCopy(pos, oldPos); PosAddDV(pos, crouchingState, dvec); time += LE_ActorGetStepTime(le, pos, oldPos, dir, NET_ReadShort(msg)); NET_ReadShort(msg); } nextTime += time + 400; } break; case EV_ACTOR_SHOOT: { const fireDef_t *fd; int flags, dummy; int objIdx, surfaceFlags; objDef_t *obj; int weap_fds_idx, fd_idx; shoot_types_t shootType; vec3_t muzzle, impact; /* read data */ NET_ReadFormat(msg, eventData->formatString, &dummy, &dummy, &dummy, &objIdx, &weap_fds_idx, &fd_idx, &shootType, &flags, &surfaceFlags, &muzzle, &impact, &dummy); obj = INVSH_GetItemByIDX(objIdx); fd = FIRESH_GetFiredef(obj, weap_fds_idx, fd_idx); if (!(flags & SF_BOUNCED)) { /* shooting */ if (fd->speed > 0.0 && !CL_OutsideMap(impact, UNIT_SIZE * 10)) { impactTime = shootTime + 1000 * VectorDist(muzzle, impact) / fd->speed; } else { impactTime = shootTime; } if (cl.actTeam != cls.team) nextTime = impactTime + 1400; else nextTime = impactTime + 400; if (fd->delayBetweenShots > 0.0) shootTime += 1000 / fd->delayBetweenShots; } else { /* only a bounced shot */ eventTime = impactTime; if (fd->speed > 0.0) { impactTime += 1000 * VectorDist(muzzle, impact) / fd->speed; nextTime = impactTime; } } parsedDeath = qfalse; } break; case EV_ACTOR_THROW: nextTime += NET_ReadShort(msg); impactTime = shootTime = nextTime; parsedDeath = qfalse; break; default: break; } Com_DPrintf(DEBUG_EVENTSYS, "%s => eventTime: %i, nextTime: %i, impactTime: %i, shootTime: %i\n", eventData->name, eventTime, nextTime, impactTime, shootTime); return eventTime; #else if (!eventData->timeCallback) return cl.time; return eventData->timeCallback(eventData, msg, dt); #endif }
/** * @brief Tries to find a path from the given actor(-position) to a given target position * * Unlike Grid_CalcPathing, this function does not neccessarily calculate the TU values for * all positions reachable from 'from'. Instead it tries to find the shortest/fastest path to * the target position. There is no limit to maxTUs. * * @param[in] routing Reference to client or server side routing table (clMap, svMap) * @param[in] actorSize The size of thing to calc the move for (e.g. size=2 means 2x2). * The plan is to have the 'origin' in 2x2 units in the bottom-left (towards the lower coordinates) corner of the 2x2 square. * @param[in,out] path Pointer to client or server side pathing table (clMap, svMap) * @param[in] from The position to start the calculation from. * @param[in] targetPos The position where we want to end up. * @param[in] maxTUs The maximum TUs away from 'from' to calculate move-information for * @param[in] crouchingState Whether the actor is currently crouching, 1 is yes, 0 is no. * @param[in] fb_list Forbidden list (entities are standing at those points) * @param[in] fb_length Length of forbidden list * @sa G_MoveCalc * @sa CL_ConditionalMoveCalc */ bool Grid_FindPath (const Routing &routing, const actorSizeEnum_t actorSize, pathing_t *path, const pos3_t from, const pos3_t targetPos, byte crouchingState, int maxTUs, byte ** fb_list, int fb_length) { bool found = false; int count; priorityQueue_t pqueue; pos4_t epos; /**< Extended position; includes crouching state */ pos3_t pos; /* this is the position of the current actor- so the actor can stand in the cell it is in when pathfinding */ pos3_t excludeFromForbiddenList; /* Confirm bounds */ assert((from[2]) < PATHFINDING_HEIGHT); assert(crouchingState == 0 || crouchingState == 1); /* s.a. ACTOR_MAX_STATES */ /* reset move data */ OBJSET(path->area, ROUTING_NOT_REACHABLE); OBJSET(path->areaFrom, ROUTING_NOT_REACHABLE); path->fblist = fb_list; path->fblength = fb_length; /* Prepare exclusion of starting-location (i.e. this should be ent-pos or le-pos) in Grid_CheckForbidden */ VectorCopy(from, excludeFromForbiddenList); /* set starting position to 0 TUs.*/ RT_AREA_POS(path, from, crouchingState) = 0; PQueueInitialise(&pqueue, 1024); Vector4Set(epos, from[0], from[1], from[2], crouchingState); PQueuePush(&pqueue, epos, 0); count = 0; while (!PQueueIsEmpty(&pqueue)) { PQueuePop(&pqueue, epos); VectorCopy(epos, pos); count++; /* if reaching that square already took too many TUs, * don't bother to reach new squares *from* there. */ const byte usedTUs = RT_AREA_POS(path, pos, crouchingState); if (usedTUs >= maxTUs) continue; for (int dir = 0; dir < PATHFINDING_DIRECTIONS; dir++) { Step step(routing, pos, actorSize, crouchingState, dir); /* Directions 12, 14, and 15 are currently undefined. */ if (dir == 12 || dir == 14 || dir == 15) continue; /* If this is a crouching or crouching move, forget it. */ if (dir == DIRECTION_STAND_UP || dir == DIRECTION_CROUCH) continue; if (!step.init()) continue; /* either dir is irrelevant or something worse happened */ if (!step.isPossible(path)) continue; /* Is this a better move into this cell? */ RT_AREA_TEST_POS(path, step.toPos, step.crouchingState); if (RT_AREA_POS(path, step.toPos, step.crouchingState) <= step.TUsAfter) { continue; /* This move is not optimum. */ } /* Test for forbidden (by other entities) areas. */ /* Do NOT check the forbiddenList. We might find a multi-turn path. */ #if 0 if (Grid_CheckForbidden(excludeFromForbiddenList, step.actorSize, path, step.toPos[0], step.toPos[1], step.toPos[2])) { continue; /* That spot is occupied. */ } #endif /* Store move in pathing table. */ Grid_SetMoveData(path, step.toPos, step.crouchingState, step.TUsAfter, step.dir, step.fromPos[2]); pos4_t dummy; const int dist = step.TUsAfter + (int) (2 * VectorDist(step.toPos, targetPos)); Vector4Set(dummy, step.toPos[0], step.toPos[1], step.toPos[2], step.crouchingState); PQueuePush(&pqueue, dummy, dist); if (VectorEqual(step.toPos, targetPos)) { found = true; break; } } if (found) break; } /* Com_Printf("Loop: %i", count); */ PQueueFree(&pqueue); return found; }
/** * @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"); }