void CL_ActorStateChange (const eventRegister_t *self, struct dbuffer *msg) { le_t *le; int entnum, state; character_t *chr; NET_ReadFormat(msg, self->formatString, &entnum, &state); le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); if (!LE_IsActor(le)) { Com_Printf("StateChange message ignored... LE is no actor (number: %i, state: %i, type: %i)\n", entnum, state, le->type); return; } /* If standing up or crouching down remove the reserved-state for crouching. */ if (((state & STATE_CROUCHED) && !LE_IsCrouched(le)) || (!(state & STATE_CROUCHED) && LE_IsCrouched(le))) { if (CL_ActorUsableTUs(le) < TU_CROUCH && CL_ActorReservedTUs(le, RES_CROUCH) >= TU_CROUCH) { /* We have not enough non-reserved TUs, * but some reserved for crouching/standing up. * i.e. we only reset the reservation for crouching if it's the very last attempt. */ CL_ActorReserveTUs(le, RES_CROUCH, 0); /* Reset reserved TUs (0 TUs) */ } } /* killed by the server: no animation is played, etc. */ if ((state & STATE_DEAD) && LE_IsLivingActor(le)) { le->state = state; FLOOR(le) = NULL; LE_SetThink(le, NULL); VectorCopy(player_dead_maxs, le->maxs); CL_ActorRemoveFromTeamList(le); return; } else { le->state = state; LE_SetThink(le, LET_StartIdle); } /* save those states that the actor should also carry over to other missions */ chr = CL_ActorGetChr(le); if (!chr) return; chr->state = (le->state & STATE_REACTION); /* change reaction button state */ if (!(le->state & STATE_REACTION)) { UI_ExecuteConfunc("disable_reaction"); } else { UI_ExecuteConfunc("startreaction"); } /* state change may have affected move length */ CL_ActorConditionalMoveCalc(le); }
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 draw a simple 'spotted' line from a spotter to the spotted */ static void CL_DrawLineOfSight (const le_t *watcher, const le_t *target) { ptl_t *ptl; vec3_t eyes; if (!watcher || !target) return; /* start is the watchers origin */ VectorCopy(watcher->origin, eyes); if (LE_IsCrouched(watcher)) eyes[2] += EYE_HT_CROUCH; else eyes[2] += EYE_HT_STAND; ptl = CL_ParticleSpawn("fadeTracer", 0, eyes, target->origin); if (LE_IsCivilian(target)) VectorSet(ptl->color, 0.2, 0.2, 1); }
/** * @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 Plays sound of content for moving actor. * @param[in] le Pointer to local entity being an actor. * @param[in] contents The contents flag of the brush we are currently in * @note Currently it supports only CONTENTS_WATER, any other special contents * can be added here anytime. */ static void LE_PlaySoundFileForContents (le_t* le, int contents) { /* only play those water sounds when an actor jumps into the water - but not * if he enters carefully in crouched mode */ if (!LE_IsCrouched(le)) { if (contents & CONTENTS_WATER) { /* were we already in the water? */ if (le->positionContents & CONTENTS_WATER) { /* play water moving sound */ S_PlayStdSample(SOUND_WATER_MOVE, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS); } else { /* play water entering sound */ S_PlayStdSample(SOUND_WATER_IN, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS); } return; } if (le->positionContents & CONTENTS_WATER) { /* play water leaving sound */ S_PlayStdSample(SOUND_WATER_OUT, le->origin, SOUND_ATTN_IDLE, SND_VOLUME_FOOTSTEPS); } } }
/** * @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 chance to hit if the actor has a fire mode activated. * @param[in] actor The local entity of the actor to calculate the hit probability for. * @todo The hit probability should work somewhat differently for splash damage weapons. * Since splash damage weapons can deal damage even when they don't directly hit an actor, * the hit probability should be defined as the predicted percentage of the maximum splash * damage of the firemode, assuming the projectile explodes at the desired location. This * means that a percentage should be displayed for EVERY actor in the predicted blast * radius. This will likely require specialized code. */ int CL_GetHitProbability (const le_t* actor) { vec3_t shooter, target; float distance, pseudosin, width, height, acc, perpX, perpY, hitchance, stdevupdown, stdevleftright, crouch, commonfactor; int distx, disty, n; le_t *le; const character_t *chr; pos3_t toPos; assert(actor); assert(actor->fd); if (IS_MODE_FIRE_PENDING(actor->actorMode)) VectorCopy(actor->mousePendPos, toPos); else VectorCopy(mousePos, toPos); /** @todo use LE_FindRadius */ le = LE_GetFromPos(toPos); if (!le) return 0; /* or suicide attempted */ if (le->selected && !FIRESH_IsMedikit(le->fd)) return 0; VectorCopy(actor->origin, shooter); VectorCopy(le->origin, target); /* Calculate HitZone: */ distx = fabs(shooter[0] - target[0]); disty = fabs(shooter[1] - target[1]); distance = sqrt(distx * distx + disty * disty); if (distx > disty) pseudosin = distance / distx; else pseudosin = distance / disty; width = 2 * PLAYER_WIDTH * pseudosin; height = LE_IsCrouched(le) ? PLAYER_CROUCHING_HEIGHT : PLAYER_STANDING_HEIGHT; chr = CL_ActorGetChr(actor); if (!chr) Com_Error(ERR_DROP, "No character given for local entity"); acc = GET_ACC(chr->score.skills[ABILITY_ACCURACY], actor->fd->weaponSkill ? chr->score.skills[actor->fd->weaponSkill] : 0.0) * CL_ActorInjuryModifier(actor, MODIFIER_ACCURACY); crouch = (LE_IsCrouched(actor) && actor->fd->crouch) ? actor->fd->crouch : 1.0; commonfactor = crouch * torad * distance * GET_INJURY_MULT(chr->score.skills[ABILITY_MIND], actor->HP, actor->maxHP); stdevupdown = (actor->fd->spread[0] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor; stdevleftright = (actor->fd->spread[1] * (WEAPON_BALANCE + SKILL_BALANCE * acc)) * commonfactor; hitchance = (stdevupdown > LOOKUP_EPSILON ? CL_LookupErrorFunction(height * 0.3536f / stdevupdown) : 1.0f) * (stdevleftright > LOOKUP_EPSILON ? CL_LookupErrorFunction(width * 0.3536f / stdevleftright) : 1.0f); /* 0.3536=sqrt(2)/4 */ /* Calculate cover: */ n = 0; height = height / 18; width = width / 18; target[2] -= UNIT_HEIGHT / 2; target[2] += height * 9; perpX = disty / distance * width; perpY = 0 - distx / distance * width; target[0] += perpX; perpX *= 2; target[1] += perpY; perpY *= 2; target[2] += 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] += perpX; target[1] += perpY; target[2] -= 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] += perpX; target[1] += perpY; target[2] += 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[2] += 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX * 3; target[1] -= perpY * 3; target[2] -= 12 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] += 6 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] -= 4 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; target[0] -= perpX; target[1] -= perpY; target[2] += 10 * height; if (!CL_TestLine(shooter, target, TL_FLAG_NONE)) n++; return 100 * (hitchance * (0.125) * n); }
/** * @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 }