/** * @brief Moves actor. * @param[in] self Pointer to the event structure that is currently executed * @param[in] msg The netchannel message * @sa LET_PathMove * @note EV_ACTOR_MOVE */ void CL_ActorDoMove (const eventRegister_t* self, dbuffer* msg) { const int number = NET_ReadShort(msg); /* get le */ le_t* le = LE_Get(number); if (!le) LE_NotFoundError(number); if (!LE_IsActor(le)) Com_Error(ERR_DROP, "Can't move, LE doesn't exist or is not an actor (entnum: %i, type: %i)\n", number, le->type); if (LE_IsDead(le)) Com_Error(ERR_DROP, "Can't move, actor on team %i dead (entnum: %i)", le->team, number); /* lock this le for other events, the corresponding unlock is in LE_DoEndPathMove() */ LE_Lock(le); if (le->isMoving()) { if (le->pathLength == le->pathPos) { LE_DoEndPathMove(le); } else { Com_Error(ERR_DROP, "Actor (entnum: %i) on team %i is still moving (%i steps left). Times: %i, %i, %i", le->entnum, le->team, le->pathLength - le->pathPos, le->startTime, le->endTime, cl.time); } } int i = 0; /* the end of this event is marked with a 0 */ while (NET_PeekLong(msg) != 0) { NET_ReadByte(msg); le->dvtab[i] = NET_ReadShort(msg); /** Don't adjust dv values here- the whole thing is needed to move the actor! */ le->speed[i] = NET_ReadShort(msg); le->pathContents[i] = NET_ReadShort(msg); i++; } le->pathLength = i; if (le->pathLength > MAX_ROUTE) Com_Error(ERR_DROP, "Overflow in pathLength (entnum: %i)", number); /* skip the end of move marker */ NET_ReadLong(msg); /* Also get the final position */ NET_ReadGPos(msg, le->newPos); if (VectorCompare(le->newPos, le->pos)) Com_Error(ERR_DROP, "start and end pos are the same (entnum: %i)", number); /* activate PathMove function */ le->resetFloor(); if (LE_IsInvisible(le)) /* Hack: this relies on the visibility events interrupting the EV_ACTOR_MOVE event */ LE_SetThink(le, LET_HiddenMove); else LE_SetThink(le, LET_StartPathMove); le->pathPos = 0; le->startTime = cl.time; le->endTime = cl.time; }
/** * @brief Callback for EV_DOOR_CLOSE event - rotates the inline model and recalc routing * @sa EV_DOOR_CLOSE * @sa G_ClientUseEdict * @sa Touch_DoorTrigger */ void CL_DoorClose (const eventRegister_t *self, struct dbuffer *msg) { /* get local entity */ int number; le_t *le; NET_ReadFormat(msg, self->formatString, &number); le = LE_Get(number); if (!le) LE_NotFoundError(number); if (le->type == ET_DOOR) { if (le->dir & DOOR_OPEN_REVERSE) le->angles[le->dir & 3] += DOOR_ROTATION_ANGLE; else le->angles[le->dir & 3] -= DOOR_ROTATION_ANGLE; CM_SetInlineModelOrientation(cl.mapTiles, le->inlineModelName, le->origin, le->angles); CL_RecalcRouting(le); } else if (le->type == ET_DOOR_SLIDING) { LE_SetThink(le, LET_DoorSlidingClose); le->think(le); } else { Com_Error(ERR_DROP, "Invalid door entity found of type: %i", le->type); } }
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); }
/** * @brief Network event function for reaction fire target handling. Responsible for updating * the HUD with the information that were received from the server * @param self The event pointer * @param msg The network message to parse the event data from */ void CL_ActorReactionFireTargetUpdate (const eventRegister_t* self, dbuffer* msg) { int shooterEntNum; int targetEntNum; // if these TUs have arrived at 0, the reaction fire can be triggered int tusUntilTriggered; int unused; NET_ReadFormat(msg, self->formatString, &shooterEntNum, &targetEntNum, &tusUntilTriggered, &unused); const le_t* shooter = LE_Get(shooterEntNum); if (!shooter) LE_NotFoundError(shooterEntNum); const le_t* target = LE_Get(targetEntNum); if (!target) LE_NotFoundError(targetEntNum); const bool outOfRange = CL_ActorIsReactionFireOutOfRange(shooter, target); UI_ExecuteConfunc("reactionfire_updatetarget %i %i %i %i", shooterEntNum, target->entnum, tusUntilTriggered, outOfRange); }
/** * @brief Called whenever an entity disappears from view * @sa CL_EntAppear */ void CL_EntPerish (const eventRegister_t *self, struct dbuffer *msg) { int entnum; le_t *le, *actor; NET_ReadFormat(msg, self->formatString, &entnum); le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); /* decrease the count of spotted aliens (also stunned) */ cl.numEnemiesSpotted = CL_CountVisibleEnemies(); switch (le->type) { case ET_ITEM: cls.i.EmptyContainer(&cls.i, &le->i, INVDEF(csi.idFloor)); /* search owners (there can be many, some of them dead) */ actor = NULL; while ((actor = LE_GetNextInUse(actor))) { if ((actor->type == ET_ACTOR || actor->type == ET_ACTOR2x2) && VectorCompare(actor->pos, le->pos)) { Com_DPrintf(DEBUG_CLIENT, "CL_EntPerish: le of type ET_ITEM hidden\n"); FLOOR(actor) = NULL; } } break; case ET_ACTOR: case ET_ACTOR2x2: cls.i.DestroyInventory(&cls.i, &le->i); break; #ifdef DEBUG case ET_ACTORHIDDEN: Com_DPrintf(DEBUG_CLIENT, "CL_EntPerish: It should not happen that we perish an hidden actor\n"); return; #endif case ET_PARTICLE: CL_ParticleFree(le->ptl); le->ptl = NULL; break; case ET_BREAKABLE: case ET_DOOR: case ET_DOOR_SLIDING: break; default: break; } le->invis = qtrue; }
/** * @brief Network event function for reaction fire target handling. Responsible for updating * the HUD with the information that were received from the server * @param self The event pointer * @param msg The network message to parse the event data from */ void CL_ActorReactionFireRemoveTarget (const eventRegister_t* self, dbuffer* msg) { int shooterEntNum; int targetEntNum; int unused; NET_ReadFormat(msg, self->formatString, &shooterEntNum, &targetEntNum, &unused); const le_t* target = LE_Get(targetEntNum); if (!target) LE_NotFoundError(targetEntNum); UI_ExecuteConfunc("reactionfire_removetarget %i %i", shooterEntNum, target->entnum); }
/** * @sa CL_InvDel * @sa G_SendInventory * @sa EV_INV_ADD */ void CL_InvAdd (const eventRegister_t *self, dbuffer *msg) { const int number = NET_ReadShort(msg); le_t *le = LE_Get(number); int nr = NET_ReadShort(msg); if (!le) LE_NotFoundError(number); le->removeNextFrame = false; for (; nr-- > 0;) { item_t item; containerIndex_t container; int x, y; CL_NetReceiveItem(msg, &item, &container, &x, &y); if (LE_IsItem(le)) { if (container != csi.idFloor) Com_Error(ERR_DROP, "InvAdd for ET_ITEM but target container is not the floor but %i", container); } else if (INVDEF(container)->temp) { Com_Error(ERR_DROP, "InvAdd for %i to temp container %i", le->type, container); } if (cls.i.AddToInventory(&cls.i, &le->i, &item, INVDEF(container), x, y, item.amount) == NULL) Com_Error(ERR_DROP, "InvAdd failed - could not add %i item(s) of %s to container %i", item.amount, item.item->id, container); if (container == csi.idRight) le->right = item.item->idx; else if (container == csi.idLeft) le->left = item.item->idx; else if (container == csi.idExtension) le->extension = item.item->idx; else if (container == csi.idHeadgear) le->headgear = item.item->idx; } switch (le->type) { case ET_ACTOR: case ET_ACTOR2x2: LE_SetThink(le, LET_StartIdle); break; case ET_ITEM: LE_PlaceItem(le); break; default: break; } }
/** * @sa CL_InvDel * @sa G_SendInventory * @sa EV_INV_ADD */ void CL_InvAdd (const eventRegister_t* self, dbuffer* msg) { const int number = NET_ReadShort(msg); le_t* le = LE_Get(number); int nr = NET_ReadShort(msg); if (!le) LE_NotFoundError(number); le->flags &= ~LE_REMOVE_NEXT_FRAME; for (; nr-- > 0;) { Item item; containerIndex_t container; int x, y; CL_NetReceiveItem(msg, &item, &container, &x, &y); if (LE_IsItem(le)) { if (container != CID_FLOOR) Com_Error(ERR_DROP, "InvAdd for ET_ITEM but target container is not the floor but %i", container); } else if (INVDEF(container)->temp) { Com_Error(ERR_DROP, "InvAdd for %i to temp container %i", le->type, container); } if (cls.i.addToInventory(&le->inv, &item, INVDEF(container), x, y, item.getAmount()) == nullptr) Com_Error(ERR_DROP, "InvAdd failed - could not add %i item(s) of %s to container %i", item.getAmount(), item.def()->id, container); if (container == CID_RIGHT) le->right = item.def()->idx; else if (container == CID_LEFT) le->left = item.def()->idx; else if (container == CID_HEADGEAR) le->headgear = item.def()->idx; } switch (le->type) { case ET_ACTOR: case ET_ACTOR2x2: if (LE_IsSelected(le)) Cmd_ExecuteString("hud_updateactorload"); LE_SetThink(le, LET_StartIdle); break; case ET_ITEM: LE_PlaceItem(le); break; default: break; } }
/** * @brief Reads the entity number for client interaction * @sa EV_CLIENT_ACTION * @sa Touch_DoorTrigger * @sa CL_ActorUse * @todo Hud should have a button that should be activated now */ void CL_ActorClientAction (const eventRegister_t *self, dbuffer *msg) { le_t* le; int number, actionEntityNumber; /* read data */ NET_ReadFormat(msg, self->formatString, &number, &actionEntityNumber); /* get actor le */ le = LE_Get(number); if (!le) LE_NotFoundError(number); /* set client action entity */ le->clientAction = LE_Get(actionEntityNumber); if (!le->clientAction) LE_NotFoundError(actionEntityNumber); UI_ExecuteConfunc("enable_clientaction"); Com_DPrintf(DEBUG_CLIENT, "CL_ActorClientAction: Set entity number: %i (for actor with entnum %i)\n", actionEntityNumber, number); }
/** * @note e.g. func_breakable or func_door with health * @sa EV_MODEL_EXPLODE */ void CL_Explode (const eventRegister_t *self, struct dbuffer *msg) { const int entnum = NET_ReadShort(msg); le_t *le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); le->inuse = false; if (le->modelnum1 > 0) cl.model_clip[le->modelnum1] = NULL; /* Recalc the client routing table because this le (and the inline model) is now gone */ CL_RecalcRouting(le); }
/** * @brief Network event function for TU reservation. Responsible for updating the HUD with the information * that were received from the server * @param self The event pointer * @param msg The network message to parse the event data from */ void CL_ActorReservationChange (const eventRegister_t* self, dbuffer* msg) { int entnum, reaction, shot, crouch; NET_ReadFormat(msg, self->formatString, &entnum, &reaction, &shot, &crouch); const le_t* le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); character_t* chr = CL_ActorGetChr(le); if (!chr) return; chr->reservedTus.reaction = reaction; chr->reservedTus.shot = shot; chr->reservedTus.crouch = crouch; }
int CL_ActorReactionFireRemoveTargetTime (const eventRegister_t* self, dbuffer* msg, eventTiming_t* eventTiming) { int targetEntNum; int unused; int step; NET_ReadFormat(msg, self->formatString, &unused, &targetEntNum, &step); const le_t* target = LE_Get(targetEntNum); if (!target) LE_NotFoundError(targetEntNum); if (step >= MAX_ROUTE) return eventTiming->nextTime; const int stepTime = CL_GetStepTime(eventTiming, target, step); if (eventTiming->shootTime > stepTime) return eventTiming->impactTime; return stepTime; }
/** * @brief Turns actor. * @param[in] self Pointer to the event structure that is currently executed * @param[in] msg The netchannel message */ void CL_ActorDoTurn (const eventRegister_t* self, dbuffer* msg) { int entnum, dir; NET_ReadFormat(msg, self->formatString, &entnum, &dir); /* get le */ le_t* le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); if (!LE_IsActor(le)) Com_Error(ERR_DROP, "Can't turn, LE doesn't exist or is not an actor (number: %i, type: %i)\n", entnum, le->type); if (LE_IsDead(le)) Com_Error(ERR_DROP, "Can't turn, actor dead\n"); le->angle = dir; le->angles[YAW] = directionAngles[le->angle]; }
int CL_SoundEventTime (const struct eventRegister_s* self, dbuffer* msg, eventTiming_t* eventTiming) { char sound[MAX_QPATH]; vec3_t origin; int number; int step; /* read data */ NET_ReadFormat(msg, self->formatString, &number, &origin, &step, &sound, sizeof(sound)); const le_t* le = LE_Get(number); if (!le) LE_NotFoundError(number); if (step >= MAX_ROUTE) return eventTiming->nextTime; const int stepTime = CL_GetStepTime(eventTiming, le, step); if (eventTiming->shootTime > stepTime) return eventTiming->impactTime; return stepTime; }
/** * @brief Network event function for reaction fire mode changes. Responsible for updating * the HUD with the information that were received from the server * @param self The event pointer * @param msg The network message to parse the event data from * @sa HUD_UpdateReactionFiremodes */ void CL_ActorReactionFireChange (const eventRegister_t* self, dbuffer* msg) { actorHands_t hand; int entnum, fmIdx, odIdx; NET_ReadFormat(msg, self->formatString, &entnum, &fmIdx, &hand, &odIdx); const le_t* le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); character_t* chr = CL_ActorGetChr(le); if (!chr) return; const objDef_t* od = INVSH_GetItemByIDX(odIdx); chr->RFmode.set(hand, fmIdx, od); UI_ExecuteConfunc("reactionfire_updated"); }
/** * @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 Callback for EV_DOOR_CLOSE event - rotates the inline model and recalc routing * @sa EV_DOOR_CLOSE * @sa G_ClientUseEdict * @sa Touch_DoorTrigger */ void CL_DoorClose (const eventRegister_t* self, dbuffer* msg) { /* get local entity */ int number; NET_ReadFormat(msg, self->formatString, &number); le_t* le = LE_Get(number); if (!le) LE_NotFoundError(number); if (le->type == ET_DOOR) { LE_SetThink(le, LET_DoorRotatingClose); le->think(le); } else if (le->type == ET_DOOR_SLIDING) { LE_SetThink(le, LET_DoorSlidingClose); le->think(le); } else { Com_Error(ERR_DROP, "Invalid door entity found of type: %i", le->type); } }
/** * @brief Let a particle appear for the client * @param[in] self Pointer to the event structure that is currently executed * @param[in] msg holds the network data * @sa CL_ParticleSpawn * @sa EV_PARTICLE_APPEAR */ void CL_ParticleAppear (const eventRegister_t* self, dbuffer* msg) { char particle[MAX_VAR]; int entnum, levelflags; vec3_t origin; /* read data */ NET_ReadFormat(msg, self->formatString, &entnum, &levelflags, origin, particle, sizeof(particle)); le_t* le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); /* particles don't have a model to add to the scene - we mark them as invisible and * only render the particle */ LE_SetInvisible(le); le->levelflags = levelflags; le->particleID = Mem_PoolStrDup(particle, cl_genericPool, 0); le->ptl = CL_ParticleSpawn(le->particleID, le->levelflags, origin); if (!le->ptl) Com_Printf("Could not spawn particle: '%s'\n", le->particleID); }
/** * @brief Parses the actor stats that comes from the netchannel * @sa CL_ParseEvent * @sa G_SendStats */ void CL_ActorStats (const eventRegister_t *self, dbuffer *msg) { le_t *le; int entnum, oldTUs = 0; entnum = NET_ReadShort(msg); le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); switch (le->type) { case ET_ACTORHIDDEN: case ET_ACTOR: case ET_ACTOR2x2: break; default: Com_Printf("CL_ActorStats: LE (%i) not an actor (type: %i)\n", entnum, le->type); return; } if (LE_IsSelected(le)) oldTUs = le->TU; NET_ReadFormat(msg, self->formatString, &le->TU, &le->HP, &le->STUN, &le->morale); if (le->TU > le->maxTU) le->maxTU = le->TU; if (le->HP > le->maxHP) le->maxHP = le->HP; if (le->morale > le->maxMorale) le->maxMorale = le->morale; /* if selActor's timeunits have changed, update movelength */ if (le->TU != oldTUs && LE_IsSelected(le)) CL_ActorResetMoveLength(le); }
/** * @brief Parses the actor wound stats that come from the netchannel * @sa CL_ParseEvent * @sa G_SendStats */ void CL_ActorWound (const eventRegister_t *self, struct dbuffer *msg) { le_t *le; int entnum, bodyPart, wounds, treatment; NET_ReadFormat(msg, self->formatString, &entnum, &bodyPart, &wounds, &treatment); le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); switch (le->type) { case ET_ACTORHIDDEN: case ET_ACTOR: case ET_ACTOR2x2: break; default: Com_Printf("CL_ActorWound: LE (%i) not an actor (type: %i)\n", entnum, le->type); return; } le->wounds.woundLevel[bodyPart] = wounds; le->wounds.treatmentLevel[bodyPart] = treatment; }
/** * @brief Revitalizes a stunned actor (all that is needed is the local entity state set). * @param[in] msg The netchannel message * @param[in] self Pointer to the event structure that is currently executed */ void CL_ActorRevitalised (const eventRegister_t* self, dbuffer* msg) { int entnum, state; NET_ReadFormat(msg, self->formatString, &entnum, &state); /* get les */ le_t* le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); if (!LE_IsStunned(le) && !LE_IsLivingActor(le)) Com_Error(ERR_DROP, "CL_ActorRevitalised: Can't revitalise, LE is not a dead or stunned actor"); LE_Lock(le); /* link any floor container into the actor temp floor container */ le_t* floor = LE_Find(ET_ITEM, le->pos); if (floor) le->setFloor(floor); le->state = state; /* play animation */ LE_SetThink(le, LET_StartIdle); /* Print some info. */ if (le->team == cls.team) { const character_t* chr = CL_ActorGetChr(le); if (chr) { char tmpbuf[128]; Com_sprintf(tmpbuf, lengthof(tmpbuf), _("%s was revitalised\n"), chr->name); HUD_DisplayMessage(tmpbuf); } } else { switch (le->team) { case (TEAM_CIVILIAN): HUD_DisplayMessage(_("A civilian was revitalised.")); break; case (TEAM_ALIEN): HUD_DisplayMessage(_("An alien was revitalised.")); break; case (TEAM_PHALANX): HUD_DisplayMessage(_("A soldier was revitalised.")); break; default: HUD_DisplayMessage(va(_("A member of team %i was revitalised."), le->team)); break; } } le->aabb.setMaxs(player_maxs); if (le->ptl) { CL_ParticleFree(le->ptl); le->ptl = nullptr; } /* add team members to the actor list */ CL_ActorAddToTeamList(le); /* update pathing as we maybe not can walk onto this actor anymore */ CL_ActorConditionalMoveCalc(selActor); LE_Unlock(le); }
/** * @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 }
/** * @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; }
/** * @sa CL_ActorAddToTeamList * @sa G_AppearPerishEvent * @sa CL_ActorAdd * @note EV_ACTOR_APPEAR */ void CL_ActorAppear (const eventRegister_t *self, struct dbuffer *msg) { le_t *le, *leResponsible; int entnum, entnumResponsible, modelnum1, modelnum2; int teamDefID = -1; /* check if the actor is already visible */ entnum = NET_ReadShort(msg); entnumResponsible = NET_ReadShort(msg); le = LE_Get(entnum); leResponsible = LE_Get(entnumResponsible); if (entnumResponsible != SKIP_LOCAL_ENTITY && !leResponsible) LE_NotFoundError(entnumResponsible); /* mission start - no actor is spawned yet - so create it */ if (!le) le = LE_Add(entnum); /* Locking should be unnecessary if CL_CheckDefault filters this call, since this event starts and * ends in this function only. Adding lock/unlock just to be sure. */ LE_Lock(le); /* maybe added via CL_ActorAdd before */ le->invis = false; /* get the info */ NET_ReadFormat(msg, self->formatString, &le->team, &teamDefID, &le->gender, &le->ucn, &le->pnum, &le->pos, &le->angle, &le->right, &le->left, &modelnum1, &modelnum2, &le->bodySkin, &le->headSkin, &le->state, &le->fieldSize, &le->maxTU, &le->maxMorale, &le->maxHP); if (teamDefID < 0 || teamDefID > csi.numTeamDefs) Com_Printf("CL_ActorAppear: Invalid teamDef index\n"); else le->teamDef = &csi.teamDef[teamDefID]; switch (le->fieldSize) { case ACTOR_SIZE_NORMAL: le->addFunc = CL_AddActor; le->type = ET_ACTOR; break; case ACTOR_SIZE_2x2: le->addFunc = CL_AddUGV; le->type = ET_ACTOR2x2; break; default: Com_Error(ERR_DROP, "Unknown fieldSize for le in CL_ActorAppear (EV_ACTOR_APPEAR)"); } le->modelnum1 = modelnum1; le->modelnum2 = modelnum2; le->model1 = LE_GetDrawModel(modelnum1); le->model2 = LE_GetDrawModel(modelnum2); Grid_PosToVec(cl.mapData->map, le->fieldSize, le->pos, le->origin); le->angles[YAW] = directionAngles[le->angle]; if (LE_IsDead(le) && !LE_IsStunned(le)) le->contents = CONTENTS_DEADACTOR; else le->contents = CONTENTS_ACTOR; VectorCopy(player_mins, le->mins); if (LE_IsDead(le)) VectorCopy(player_dead_maxs, le->maxs); else VectorCopy(player_maxs, le->maxs); LE_SetThink(le, LET_StartIdle); /* count spotted aliens (also stunned) */ cl.numEnemiesSpotted = CL_CountVisibleEnemies(); if (LE_IsLivingActor(le)) { if (cl.actTeam != cls.team) { /* center view (if wanted) */ LE_CenterView(le); } /* draw line of sight */ if (le->team != cls.team) { if (leResponsible) CL_DrawLineOfSight(leResponsible, le); /* message */ if (le->team != TEAM_CIVILIAN) { if (GAME_TeamIsKnown(le->teamDef)) { char tmpbuf[128]; Com_sprintf(tmpbuf, sizeof(tmpbuf), _("Enemy spotted: %s!"), _(le->teamDef->name)); HUD_DisplayMessage(tmpbuf); } else HUD_DisplayMessage(_("Unknown enemy spotted!")); } else HUD_DisplayMessage(_("Civilian spotted.")); /* update pathing as new actor could block path */ CL_ActorConditionalMoveCalc(leResponsible ? leResponsible : selActor); } } /* add team members to the actor list */ CL_ActorAddToTeamList(le); LE_Unlock(le); }
/** * @brief Shoot with weapon. * @sa CL_ActorShoot * @sa CL_ActorShootHidden * @todo Improve detection of left- or right animation. * @sa EV_ACTOR_SHOOT */ void CL_ActorDoShoot (const eventRegister_t* self, dbuffer* msg) { vec3_t muzzle, impact; int flags, normal, shooterEntnum, victimEntnum; int objIdx; int first; weaponFireDefIndex_t weapFdsIdx; fireDefIndex_t fdIdx; int surfaceFlags; shoot_types_t shootType; /* read data */ NET_ReadFormat(msg, self->formatString, &shooterEntnum, &victimEntnum, &first, &objIdx, &weapFdsIdx, &fdIdx, &shootType, &flags, &surfaceFlags, &muzzle, &impact, &normal); le_t* leVictim; if (victimEntnum != SKIP_LOCAL_ENTITY) { leVictim = LE_Get(victimEntnum); if (!leVictim) LE_NotFoundError(victimEntnum); } else { leVictim = nullptr; } /* get shooter le */ le_t* leShooter = LE_Get(shooterEntnum); /* get the fire def */ const objDef_t* obj = INVSH_GetItemByIDX(objIdx); const fireDef_t* fd = FIRESH_GetFiredef(obj, weapFdsIdx, fdIdx); CL_ActorGetMuzzle(leShooter, muzzle, shootType); /* add effect le */ LE_AddProjectile(fd, flags, muzzle, impact, normal, leVictim); /* start the sound */ if ((first || !fd->soundOnce) && fd->fireSound != nullptr && !(flags & SF_BOUNCED)) S_LoadAndPlaySample(fd->fireSound, muzzle, fd->fireAttenuation, SND_VOLUME_WEAPONS); /* do actor related stuff */ if (!leShooter) return; /* maybe hidden or inuse is false? */ if (!LE_IsActor(leShooter)) Com_Error(ERR_DROP, "Can't shoot, LE not an actor (type: %i)", leShooter->type); /* no animations for hidden actors */ if (leShooter->type == ET_ACTORHIDDEN) return; if (LE_IsDead(leShooter)) { Com_DPrintf(DEBUG_CLIENT, "Can't shoot, actor dead or stunned.\n"); return; } /* Animate - we have to check if it is right or left weapon usage. */ if (IS_SHOT_RIGHT(shootType)) { R_AnimChange(&leShooter->as, leShooter->model1, LE_GetAnim("shoot", leShooter->right, leShooter->left, leShooter->state)); R_AnimAppend(&leShooter->as, leShooter->model1, LE_GetAnim("stand", leShooter->right, leShooter->left, leShooter->state)); } else if (IS_SHOT_LEFT(shootType)) { R_AnimChange(&leShooter->as, leShooter->model1, LE_GetAnim("shoot", leShooter->left, leShooter->right, leShooter->state)); R_AnimAppend(&leShooter->as, leShooter->model1, LE_GetAnim("stand", leShooter->left, leShooter->right, leShooter->state)); } else if (IS_SHOT_HEADGEAR(shootType)) { if (fd->irgoggles) { leShooter->state |= RF_IRGOGGLESSHOT; if (LE_IsSelected(leShooter)) refdef.rendererFlags |= RDF_IRGOGGLES; } } else { /* no animation for headgear (yet) */ Com_Error(ERR_DROP, "CL_ActorDoShoot: Invalid shootType given (entnum: %i, shootType: %i).\n", shootType, shooterEntnum); } }
/** * @brief Kills an actor (all that is needed is the local entity state set to STATE_DEAD). * @note Also changes the animation to a random death sequence and appends the dead animation * @param[in] msg The netchannel message * @param[in] self Pointer to the event structure that is currently executed */ void CL_ActorDie (const eventRegister_t *self, dbuffer *msg) { le_t *le; int entnum, state, playerNum; NET_ReadFormat(msg, self->formatString, &entnum, &state, &playerNum); /* get les */ le = LE_Get(entnum); if (!le) LE_NotFoundError(entnum); if (!LE_IsLivingActor(le)) Com_Error(ERR_DROP, "CL_ActorDie: Can't kill, LE is not an actor (type: %i)", le->type); if (!LE_IsStunned(le) && LE_IsDead(le)) Com_Error(ERR_DROP, "CL_ActorDie: Can't kill, actor already dead"); LE_Lock(le); /* set relevant vars */ FLOOR(le) = NULL; le->state = state; /* count spotted aliens */ cl.numEnemiesSpotted = CL_CountVisibleEnemies(); /* play animation */ LE_SetThink(le, NULL); R_AnimChange(&le->as, le->model1, va("death%i", LE_GetAnimationIndexForDeath(le))); R_AnimAppend(&le->as, le->model1, va("dead%i", LE_GetAnimationIndexForDeath(le))); /* Print some info about the death or stun. */ if (le->team == cls.team) { if (playerNum != le->pnum) { const char *playerName = CL_PlayerGetName(playerNum); char tmpbuf[128]; Com_sprintf(tmpbuf, lengthof(tmpbuf), _("%s lost a soldier\n"), playerName); HUD_DisplayMessage(tmpbuf); } else { const character_t *chr = CL_ActorGetChr(le); if (chr) { char tmpbuf[128]; if (LE_IsStunned(le)) { Com_sprintf(tmpbuf, lengthof(tmpbuf), _("%s was stunned\n"), chr->name); } else { Com_sprintf(tmpbuf, lengthof(tmpbuf), _("%s was killed\n"), chr->name); } HUD_DisplayMessage(tmpbuf); } } } else { switch (le->team) { case (TEAM_CIVILIAN): if (LE_IsStunned(le)) HUD_DisplayMessage(_("A civilian was stunned.")); else HUD_DisplayMessage(_("A civilian was killed.")); break; case (TEAM_ALIEN): if (LE_IsStunned(le)) HUD_DisplayMessage(_("An alien was stunned.")); else HUD_DisplayMessage(_("An alien was killed.")); break; case (TEAM_PHALANX): if (LE_IsStunned(le)) HUD_DisplayMessage(_("A soldier was stunned.")); else HUD_DisplayMessage(_("A soldier was killed.")); break; default: if (LE_IsStunned(le)) HUD_DisplayMessage(va(_("A member of team %i was stunned."), le->team)); else HUD_DisplayMessage(va(_("A member of team %i was killed."), le->team)); break; } } /** * @todo CHRSH_IsTeamDefRobot: spawn smoke particles for robots */ CL_ActorPlaySound(le, SND_DEATH); VectorCopy(player_dead_maxs, le->maxs); if (!LE_IsStunned(le)) le->contents = CONTENTS_DEADACTOR; CL_ActorRemoveFromTeamList(le); /* update pathing as we maybe can walk onto the dead actor now */ CL_ActorConditionalMoveCalc(selActor); LE_Unlock(le); }