/** * @brief Fills a vector with the eye position of a given actor * @param[in] actor The actor edict to get the eye position in the world from * @param[out] eye The eye vector world position */ void G_ActorGetEyeVector (const Edict* actor, vec3_t eye) { VectorCopy(actor->origin, eye); if (G_IsCrouched(actor) || G_IsPanicked(actor)) eye[2] += EYE_CROUCH; else eye[2] += EYE_STAND; }
/** * @brief Changes the state of a player/soldier. * @param[in,out] player The player who controlls the actor * @param[in] ent the edict to perform the state change for * @param[in] reqState The bit-map of the requested state change * @param[in] checkaction only activate the events - network stuff is handled in the calling function * don't even use the G_ActionCheckForCurrentTeam function * @note Use checkaction true only for e.g. spawning values */ void G_ClientStateChange (const player_t* player, edict_t* ent, int reqState, bool checkaction) { /* Check if any action is possible. */ if (checkaction && !G_ActionCheckForCurrentTeam(player, ent, 0)) return; if (!reqState) return; switch (reqState) { case STATE_CROUCHED: /* Toggle between crouch/stand. */ /* Check if player has enough TUs (TU_CROUCH TUs for crouch/uncrouch). */ if (!checkaction || G_ActionCheckForCurrentTeam(player, ent, TU_CROUCH)) { if (G_IsCrouched(ent)) { if (!gi.CanActorStandHere(ent->fieldSize, ent->pos)) break; } G_ToggleCrouched(ent); G_ActorUseTU(ent, TU_CROUCH); G_ActorSetMaxs(ent); } break; case ~STATE_REACTION: /* Request to turn off reaction fire. */ if (G_IsReaction(ent)) { if (G_IsShaken(ent)) { G_ClientPrintf(player, PRINT_HUD, _("Currently shaken, won't let their guard down.")); } else { /* Turn off reaction fire. */ G_RemoveReaction(ent); G_ActorReserveTUs(ent, 0, ent->chr.reservedTus.shot, ent->chr.reservedTus.crouch); } } break; /* Request to turn on multi- or single-reaction fire mode. */ case STATE_REACTION: /* Disable reaction fire. */ G_RemoveReaction(ent); if (G_ReactionFireSettingsReserveTUs(ent)) { /* Enable requested reaction fire. */ G_SetState(ent, reqState); } break; default: gi.DPrintf("G_ClientStateChange: unknown request %i, ignoring\n", reqState); return; } /* Only activate the events - network stuff is handled in the calling function */ if (!checkaction) return; G_ClientStateChangeUpdate(ent); }
/** * @brief Sets correct bounding box for actor (state dependent). * @param[in] ent Pointer to entity for which bounding box is being set. * @note Also re-links the actor edict - because the server must know about the * changed bounding box for tracing to work. */ void G_ActorSetMaxs (Edict* ent) { if (G_IsCrouched(ent)) VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_CROUCH); else if (G_IsDead(ent) && !CHRSH_IsTeamDefRobot(ent->chr.teamDef)) VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_DEAD); else VectorSet(ent->entBox.maxs, PLAYER_WIDTH, PLAYER_WIDTH, PLAYER_STAND); /* Link it. */ gi.LinkEdict(ent); }
/** * @todo Use this function to enable footsteps over network * @note Only play the water sounds if actor is not in STATE_CROUCHED mode * we can assume, that moving slower and more carefully would also not produce * the water sounds */ void G_PhysicsStep (edict_t *ent) { /** * @todo don't play foot step sounds for flying units. */ if (ent->moveinfo.currentStep < ent->moveinfo.steps) { const int contentFlags = ent->contentFlags; const vismask_t visflags = ent->moveinfo.visflags[ent->moveinfo.currentStep]; /* Send the sound effect to everyone how's not seeing the actor */ if (!G_IsCrouched(ent)) { if (contentFlags & CONTENTS_WATER) { if (ent->moveinfo.contentFlags[ent->moveinfo.currentStep] & CONTENTS_WATER) { /* looks like we already are in the water */ /* send water moving sound */ G_EventSpawnSound(~G_VisToPM(visflags), true, ent, ent->origin, "footsteps/water_under"); } else { /* send water entering sound */ G_EventSpawnSound(~G_VisToPM(visflags), true, ent, ent->origin, "footsteps/water_in"); } } else if (ent->contentFlags & CONTENTS_WATER) { /* send water leaving sound */ G_EventSpawnSound(~G_VisToPM(visflags), true, ent, ent->origin, "footsteps/water_out"); } else { trace_t trace; vec3_t from, to; VectorCopy(ent->origin, to); VectorCopy(ent->origin, from); /* we should really hit the ground with this */ to[2] -= UNIT_HEIGHT; trace = G_Trace(from, to, NULL, MASK_SOLID); if (trace.surface) { const char *snd = gi.GetFootstepSound(trace.surface->name); if (snd) G_EventSpawnSound(~G_VisToPM(visflags), true, ent, ent->origin, snd); } } } /* and now save the new contents */ ent->contentFlags = ent->moveinfo.contentFlags[ent->moveinfo.currentStep]; ent->moveinfo.currentStep++; /* Immediately re-think */ ent->nextthink = (level.framenum + 3) * SERVER_FRAME_SECONDS; } else { ent->moveinfo.currentStep = 0; ent->moveinfo.steps = 0; ent->think = NULL; } }
/** * @brief Toggles crouch state with true/false and returns current crouch state. */ static int AIL_crouch (lua_State *L) { if (lua_gettop(L) > 0) { if (lua_isboolean(L, 1)) { const int state = lua_toboolean(L, 1); G_ClientStateChange(AIL_player, AIL_ent, STATE_CROUCHED, (state) ? true : false); } else AIL_invalidparameter(1); } lua_pushboolean(L, G_IsCrouched(AIL_ent)); return 1; }
/** * @brief Makes the actor head to the position. */ static int pos3L_goto (lua_State *L) { pos3_t *pos; const byte crouchingState = G_IsCrouched(AIL_ent) ? 1 : 0; assert(lua_ispos3(L, 1)); /* Calculate move table. */ G_MoveCalc(0, AIL_ent, AIL_ent->pos, crouchingState, G_ActorUsableTUs(AIL_ent)); gi.MoveStore(level.pathingMap); /* Move. */ pos = lua_topos3(L, 1); G_ClientMove(AIL_player, 0, AIL_ent, *pos); lua_pushboolean(L, 1); return 1; }
/** * @brief Return the needed TUs to walk to a given position * @param ent Edict to calculate move length for * @param path Pointer to pathing table * @param to Position to walk to * @param stored Use the stored mask (the cached move) of the routing data * @return ROUTING_NOT_REACHABLE if the move isn't possible, length of move otherwise (TUs) */ byte G_ActorMoveLength (const edict_t *ent, const pathing_t *path, const pos3_t to, bool stored) { byte crouchingState = G_IsCrouched(ent) ? 1 : 0; const int length = gi.MoveLength(path, to, crouchingState, stored); int dvec, numSteps = 0; pos3_t pos; if (!length || length == ROUTING_NOT_REACHABLE) return length; VectorCopy(to, pos); while ((dvec = gi.MoveNext(level.pathingMap, pos, crouchingState)) != ROUTING_UNREACHABLE) { ++numSteps; PosSubDV(pos, crouchingState, dvec); /* We are going backwards to the origin. */ } return std::min(ROUTING_NOT_REACHABLE, length + static_cast<int>(numSteps * G_ActorGetInjuryPenalty(ent, MODIFIER_MOVEMENT))); }
/** * @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_t *from = NULL; 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 */ VectorCopy(from->origin, eye); if (G_IsCrouched(from)) eye[2] += EYE_CROUCH; else eye[2] += EYE_STAND; /* line of sight */ if (!G_TestLine(eye, point)) return true; } } /* not visible */ return false; }
/** * @brief Generates the client events that are send over the netchannel to move an actor * @param[in] player Player who is moving an actor * @param[in] visTeam The team to check the visibility for - if this is 0 we build the forbidden list * above all edicts - for the human controlled actors this would mean that clicking to a grid * position that is not reachable because an invisible actor is standing there would not result in * a single step - as the movement is aborted before. For AI movement this is in general @c 0 - but * not if they e.g. hide. * @param[in] ent Edict to move * @param[in] to The grid position to walk to * @sa CL_ActorStartMove * @sa PA_MOVE */ void G_ClientMove (const player_t * player, int visTeam, edict_t* ent, const pos3_t to) { int status, initTU; dvec_t dvtab[MAX_DVTAB]; int dir; byte numdv, length; pos3_t pos; float div; int oldState; int oldHP; bool autoCrouchRequired = false; byte crouchingState; if (VectorCompare(ent->pos, to)) return; /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_MOVE_STRAIGHT)) return; crouchingState = G_IsCrouched(ent) ? 1 : 0; oldState = oldHP = 0; /* calculate move table */ G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); /* length of ROUTING_NOT_REACHABLE means not reachable */ if (length && length >= ROUTING_NOT_REACHABLE) return; /* Autostand: check if the actor is crouched and player wants autostanding...*/ if (crouchingState && player->autostand) { /* ...and if this is a long walk... */ if (SHOULD_USE_AUTOSTAND(length)) { /* ...make them stand first. If the player really wants them to walk a long * way crouched, he can move the actor in several stages. * Uses the threshold at which standing, moving and crouching again takes * fewer TU than just crawling while crouched. */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); /* change to stand state */ crouchingState = G_IsCrouched(ent) ? 1 : 0; if (!crouchingState) { G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); autoCrouchRequired = true; } } } /* this let the footstep sounds play even over network */ ent->think = G_PhysicsStep; ent->nextthink = level.time; /* assemble dvec-encoded move data */ VectorCopy(to, pos); initTU = ent->TU; numdv = G_FillDirectionTable(dvtab, lengthof(dvtab), crouchingState, pos); /* make sure to end any other pending events - we rely on EV_ACTOR_MOVE not being active anymore */ gi.EndEvents(); /* everything ok, found valid route? */ if (VectorCompare(pos, ent->pos)) { byte* stepAmount = NULL; int usedTUs = 0; /* no floor inventory at this point */ FLOOR(ent) = NULL; while (numdv > 0) { /* A flag to see if we needed to change crouch state */ int crouchFlag; const byte oldDir = ent->dir; int dvec; /* get next dvec */ numdv--; dvec = dvtab[numdv]; /* This is the direction to make the step into */ dir = getDVdir(dvec); /* turn around first */ status = G_ActorDoTurn(ent, dir); if (status & VIS_STOP) { autoCrouchRequired = false; if (ent->moveinfo.steps == 0) usedTUs += TU_TURN; break; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; /* if something appears on our route that didn't trigger a VIS_STOP, we have to * send the turn event if this is our first step */ if (oldDir != ent->dir && ent->moveinfo.steps == 0) { G_EventActorTurn(ent); usedTUs += TU_TURN; } break; } /* decrease TUs */ div = gi.GetTUsForDirection(dir, G_IsCrouched(ent)); if ((int) (usedTUs + div) > ent->TU) break; usedTUs += div; /* This is now a flag to indicate a change in crouching - we need this for * the stop in mid move call(s), because we need the updated entity position */ crouchFlag = 0; /* Calculate the new position after the decrease in TUs, otherwise the game * remembers the false position if the time runs out */ PosAddDV(ent->pos, crouchFlag, dvec); /* slower if crouched */ if (G_IsCrouched(ent)) ent->speed = ACTOR_SPEED_CROUCHED; else ent->speed = ACTOR_SPEED_NORMAL; ent->speed *= g_actorspeed->value; if (crouchFlag == 0) { /* No change in crouch */ edict_t* clientAction; int contentFlags; vec3_t pointTrace; G_EdictCalcOrigin(ent); VectorCopy(ent->origin, pointTrace); pointTrace[2] += PLAYER_MIN; contentFlags = gi.PointContents(pointTrace); /* link it at new position - this must be done for every edict * movement - to let the server know about it. */ gi.LinkEdict(ent); /* Only the PHALANX team has these stats right now. */ if (ent->chr.scoreMission) { float truediv = gi.GetTUsForDirection(dir, 0); /* regardless of crouching ! */ if (G_IsCrouched(ent)) ent->chr.scoreMission->movedCrouched += truediv; else ent->chr.scoreMission->movedNormal += truediv; } /* write the step to the net */ G_WriteStep(ent, &stepAmount, dvec, contentFlags); /* check if player appears/perishes, seen from other teams */ G_CheckVis(ent, true); /* check for anything appearing, seen by "the moving one" */ status = G_CheckVisTeamAll(ent->team, false, ent); /* Set ent->TU because the reaction code relies on ent->TU being accurate. */ G_ActorSetTU(ent, initTU - usedTUs); clientAction = ent->clientAction; oldState = ent->state; oldHP = ent->HP; /* check triggers at new position */ if (G_TouchTriggers(ent)) { if (!clientAction) status |= VIS_STOP; } G_TouchSolids(ent, 10.0f); /* state has changed - maybe we walked on a trigger_hurt */ if (oldState != ent->state) status |= VIS_STOP; else if (oldHP != ent->HP) status |= VIS_STOP; } else if (crouchFlag == 1) { /* Actor is standing */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } else if (crouchFlag == -1) { /* Actor is crouching and should stand up */ G_ClientStateChange(player, ent, STATE_CROUCHED, false); } /* check for reaction fire */ if (G_ReactionFireOnMovement(ent)) { status |= VIS_STOP; autoCrouchRequired = false; } /* check for death */ if (((oldHP != 0 && oldHP != ent->HP) || (oldState != ent->state)) && !G_IsDazed(ent)) { /** @todo Handle dazed via trigger_hurt */ /* maybe this was due to rf - then the G_ActorDie was already called */ if (!G_IsDead(ent)) { G_CheckDeathOrKnockout(ent, NULL, NULL, oldHP - ent->HP); } return; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv - 1)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; break; } /* Restore ent->TU because the movement code relies on it not being modified! */ G_ActorSetTU(ent, initTU); } /* submit the TUs / round down */ if (g_notu != NULL && g_notu->integer) G_ActorSetTU(ent, initTU); else G_ActorSetTU(ent, initTU - usedTUs); G_SendStats(ent); /* end the move */ G_GetFloorItems(ent); gi.EndEvents(); } if (autoCrouchRequired) { /* toggle back to crouched state */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } }
/** * @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 Moves the actor into a position in which he can shoot his target. */ static int AIL_positionshoot (lua_State *L) { pos3_t to, bestPos; vec3_t check; edict_t *ent; int dist; int xl, yl, xh, yh; int min_tu; aiActor_t *target; const byte crouchingState = G_IsCrouched(AIL_ent) ? 1 : 0; /* We need a target. */ assert(lua_isactor(L, 1)); target = lua_toactor(L, 1); /* Make things more simple. */ ent = AIL_ent; dist = G_ActorUsableTUs(ent); /* Calculate move table. */ G_MoveCalc(0, ent, ent->pos, crouchingState, G_ActorUsableTUs(ent)); gi.MoveStore(level.pathingMap); /* set borders */ xl = (int) ent->pos[0] - dist; if (xl < 0) xl = 0; yl = (int) ent->pos[1] - dist; if (yl < 0) yl = 0; xh = (int) ent->pos[0] + dist; if (xh > PATHFINDING_WIDTH) xl = PATHFINDING_WIDTH; yh = (int) ent->pos[1] + dist; if (yh > PATHFINDING_WIDTH) yh = PATHFINDING_WIDTH; /* evaluate moving to every possible location in the search area, * including combat considerations */ min_tu = INT_MAX; for (to[2] = 0; to[2] < PATHFINDING_HEIGHT; to[2]++) for (to[1] = yl; to[1] < yh; to[1]++) for (to[0] = xl; to[0] < xh; to[0]++) { pos_t tu; /* Can we see the target? */ gi.GridPosToVec(gi.routingMap, ent->fieldSize, to, check); tu = G_ActorMoveLength(ent, level.pathingMap, to, true); if (tu > G_ActorUsableTUs(ent) || tu == ROUTING_NOT_REACHABLE) continue; /* Better spot (easier to get to). */ if (tu < min_tu) { if (G_ActorVis(check, ent, target->ent, true) > 0.3) { VectorCopy(to, bestPos); min_tu = tu; } } } /* No position found in range. */ if (min_tu > G_ActorUsableTUs(ent)) { lua_pushboolean(L, 0); return 1; } /* Return the spot. */ lua_pushpos3(L, &bestPos); return 1; }
/** * @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; } }