static void LE_DoPathMove (le_t* le) { /* next part */ const dvec_t dvec = le->dvtab[le->pathPos]; const byte dir = getDVdir(dvec); const byte crouchingState = LE_IsCrouched(le) ? 1 : 0; /* newCrouchingState needs to be set to the current crouching state * and is possibly updated by PosAddDV. */ byte newCrouchingState = crouchingState; PosAddDV(le->pos, newCrouchingState, dvec); LE_PlayFootStepSound(le); /* only change the direction if the actor moves horizontally. */ if (dir < CORE_DIRECTIONS || dir >= FLYING_DIRECTIONS) le->angle = dir & (CORE_DIRECTIONS - 1); le->angles[YAW] = directionAngles[le->angle]; le->startTime = le->endTime; /* check for straight movement or diagonal movement */ assert(le->speed[le->pathPos]); le->endTime += LE_ActorGetStepTime(le, le->pos, le->oldPos, dir, le->speed[le->pathPos]); le->positionContents = le->pathContents[le->pathPos]; le->pathPos++; }
/** * @brief Sends the actual actor turn event over the netchannel */ static void G_ClientTurn (player_t * player, edict_t* ent, dvec_t dvec) { const int dir = getDVdir(dvec); /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_TURN)) return; /* check if we're already facing that direction */ if (ent->dir == dir) return; /* do the turn */ G_ActorDoTurn(ent, dir); G_ActorUseTU(ent, TU_TURN); /* send the turn */ G_EventActorTurn(ent); /* send the new TUs */ G_SendStats(ent); /* end the event */ G_EventEnd(); }
/* tests for the new dvec format */ static void testDvec (void) { short dv1 = 0x0724; CU_ASSERT_EQUAL(getDVdir(dv1), 0x07); CU_ASSERT_EQUAL(getDVflags(dv1), 0x02); CU_ASSERT_EQUAL(getDVz(dv1), 0x04); short dv2 = makeDV(6, 3); CU_ASSERT_EQUAL(dv2, 0x0603); dv2 = setDVz(dv2, 4); CU_ASSERT_EQUAL(dv2, 0x0604); }
/** * @brief Decides if following events should be delayed. The delay is the amount of time the actor needs to walk * from the start to the end pos. */ int CL_ActorDoMoveTime (const eventRegister_t *self, dbuffer *msg, eventTiming_t *eventTiming) { int time = 0; const int eventTime = eventTiming->nextTime; const int number = NET_ReadShort(msg); /* get le */ const le_t *le = LE_Get(number); if (!le) LE_NotFoundError(number); pos3_t pos; VectorCopy(le->pos, pos); byte crouchingState = LE_IsCrouched(le) ? 1 : 0; /* the end of this event is marked with a 0 */ while (NET_PeekLong(msg) != 0) { const dvec_t dvec = NET_ReadShort(msg); const byte dir = getDVdir(dvec); pos3_t oldPos; VectorCopy(pos, oldPos); PosAddDV(pos, crouchingState, dvec); time += LE_ActorGetStepTime(le, pos, oldPos, dir, NET_ReadShort(msg)); NET_ReadShort(msg); } /* skip the end of move marker */ NET_ReadLong(msg); /* Also skip the final position */ NET_ReadByte(msg); NET_ReadByte(msg); NET_ReadByte(msg); assert(NET_PeekByte(msg) == EV_NULL); eventTiming->nextTime += time + 400; return eventTime; }
/** * @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 Decides if following events should be delayed. The delay is the amount of time the actor needs to walk * from the start to the end pos. */ int CL_ActorDoMoveTime (const eventRegister_t* self, dbuffer* msg, eventTiming_t* eventTiming) { int time = 0; const int eventTime = eventTiming->nextTime; const int number = NET_ReadShort(msg); /* get le */ le_t* le = LE_Get(number); if (!le) LE_NotFoundError(number); pos3_t pos; VectorCopy(le->pos, pos); byte crouchingState = LE_IsCrouched(le) ? 1 : 0; leStep_t* newStep = Mem_AllocType(leStep_t); if (le->stepList == nullptr) { le->stepList = newStep; le->stepIndex = 0; } else { /* append to the list */ leStep_t* step = le->stepList; while (step) { if (step->next == nullptr) { step->next = newStep; le->stepIndex++; break; } step = step->next; } } /* the end of this event is marked with a 0 */ while (NET_PeekLong(msg) != 0) { newStep->steps = NET_ReadByte(msg); const dvec_t dvec = NET_ReadShort(msg); const byte dir = getDVdir(dvec); pos3_t oldPos; VectorCopy(pos, oldPos); PosAddDV(pos, crouchingState, dvec); const int stepTime = LE_ActorGetStepTime(le, pos, oldPos, dir, NET_ReadShort(msg)); newStep->stepTimes[newStep->steps] = stepTime; time += stepTime; NET_ReadShort(msg); } ++newStep->steps; if (newStep->steps > MAX_ROUTE) Com_Error(ERR_DROP, "route length overflow: %i", newStep->steps); /* skip the end of move marker */ NET_ReadLong(msg); /* Also skip the final position */ NET_ReadByte(msg); NET_ReadByte(msg); NET_ReadByte(msg); assert(NET_PeekByte(msg) == EV_NULL); eventTiming->nextTime += time + 400; newStep->lastMoveTime = eventTime; newStep->lastMoveDuration = time; return eventTime; }
/** * @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 }