Esempio n. 1
0
/**
 * @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;
}
Esempio n. 2
0
/**
 * @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);
}
Esempio n. 3
0
/**
 * @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);
}
Esempio n. 4
0
/**
 * @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;
	}
}
Esempio n. 5
0
/**
 * @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;
}
Esempio n. 6
0
/**
 * @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;
}
Esempio n. 7
0
/**
* @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)));
}
Esempio n. 8
0
/**
 * @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;
}
Esempio n. 9
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);
    }
}
Esempio n. 10
0
/**
 * @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;
	}
}
Esempio n. 11
0
/**
 * @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;
}
Esempio n. 12
0
/**
 * @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;
	}
}