Beispiel #1
0
/**
 * @brief Checks whether the actor should stop movement
 * @param ent The actors edict
 * @param visState The visibility check state @c VIS_PERISH, @c VIS_APPEAR
 * @return @c true if the actor should stop movement, @c false otherwise
 */
static bool G_ActorShouldStopInMidMove (const edict_t *ent, int visState, dvec_t* dvtab, int max)
{
    if (visState & VIS_STOP)
        return true;

    /* check that the appearing unit is not on a grid position the actor wanted to walk to.
     * this might be the case if the edict got visible in mid mode */
    if (visState & VIS_APPEAR) {
        pos3_t pos;
        VectorCopy(ent->pos, pos);
        while (max >= 0) {
            int tmp = 0;
            const edict_t *blockEdict;

            PosAddDV(pos, tmp, dvtab[max]);
            max--;
            blockEdict = G_GetLivingActorFromPos(pos);

            if (blockEdict && G_IsBlockingMovementActor(blockEdict)) {
                const bool visible = G_IsVisibleForTeam(blockEdict, ent->team);
                if (visible)
                    return true;
            }
        }
    }
    return false;
}
Beispiel #2
0
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 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;
}
Beispiel #4
0
/**
 * @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;
}
Beispiel #6
0
/**
 * @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
}