/** * @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; }
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); }
/** * @note Think function. * @brief Handle move for invisible actors. * @todo Is there something we should do here? */ void LET_HiddenMove (le_t* le) { VectorCopy(le->newPos, le->pos); LE_SetThink(le, LET_StartIdle); LE_ExecuteThink(le); LE_Unlock(le); }
/** * @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); } }
/** * @brief Change the animation of an actor to the idle animation (which can be * panic, dead or stand) * @note We have more than one animation for dead - the index is given by the * state of the local entity * @note Think function * @note See the *.anm files in the models dir */ void LET_StartIdle (le_t* le) { /* hidden actors don't have models assigned, thus we can not change the * animation for any model */ if (!LE_IsInvisible(le)) { if (LE_IsDead(le)) R_AnimChange(&le->as, le->model1, va("dead%i", LE_GetAnimationIndexForDeath(le))); else if (LE_IsPanicked(le)) R_AnimChange(&le->as, le->model1, "panic0"); else R_AnimChange(&le->as, le->model1, LE_GetAnim("stand", le->right, le->left, le->state)); } le->pathPos = le->pathLength = 0; if (le->stepList != nullptr) { leStep_t* step = le->stepList->next; Mem_Free(le->stepList); le->stepList = step; if (step != nullptr) { le->stepIndex--; } else if (le->stepIndex != 0) { Com_Error(ERR_DROP, "stepindex for entnum %i is out of sync (%i should be 0)\n", le->entnum, le->stepIndex); } } /* keep this animation until something happens */ LE_SetThink(le, nullptr); }
/** * @brief Slides a door * * @note Though doors, sliding doors need a very different handling: * because it's movement is animated (unlike the rotating door), * the final position that is used to calculate the routing data * is set once the animation finished (because this recalculation * might be very expensive). * * @param[in,out] le The local entity of the inline model * @param[in] speed The speed to slide with - a negative value to close the door * @sa Door_SlidingUse */ void LET_SlideDoor (le_t* le, int speed) { vec3_t moveAngles, moveDir; /* get the movement angle vector */ GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, speed, moveAngles); /* this origin is only an offset to the absolute mins/maxs for rendering */ VectorAdd(le->origin, moveAngles, le->origin); /* get the direction vector from the movement angles that were set on the entity */ AngleVectors(moveAngles, moveDir, nullptr, nullptr); moveDir[0] = fabsf(moveDir[0]); moveDir[1] = fabsf(moveDir[1]); moveDir[2] = fabsf(moveDir[2]); /* calculate the distance from the movement angles and the entity size */ const int distance = DotProduct(moveDir, le->size); bool endPos = false; if (speed > 0) { /* check whether the distance the door may slide is slided already * - if so, stop the movement of the door */ if (fabs(le->origin[le->dir & 3]) >= distance) endPos = true; } else { /* the sliding door has not origin set - except when it is opened. This door type is no * origin brush based bmodel entity. So whenever the origin vector is not the zero vector, * the door is opened. */ if (VectorEmpty(le->origin)) endPos = true; } if (endPos) { vec3_t distanceVec; /* the door finished its move - either close or open, so make sure to recalc the routing * data and set the mins/maxs for the inline brush model */ cBspModel_t* model = CM_InlineModel(cl.mapTiles, le->inlineModelName); assert(model); /* we need the angles vector normalized */ GET_SLIDING_DOOR_SHIFT_VECTOR(le->dir, (speed < 0) ? -1 : 1, moveAngles); /* the bounding box of the door is updated in one step - here is no lerping needed */ VectorMul(distance, moveAngles, distanceVec); model->cbmBox.shift(distanceVec); CL_RecalcRouting(le); /* reset the think function as the movement finished */ LE_SetThink(le, nullptr); } else le->thinkDelay = 1000; }
/** * @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); } }
/** * @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; } }
/** * @note Think function * @brief Change the actors animation to walking * @note See the *.anm files in the models dir */ void LET_StartPathMove (le_t* le) { /* center view (if wanted) */ if (!cls.isOurRound() && le->team != TEAM_CIVILIAN) LE_CenterView(le); /* initial animation or animation change */ R_AnimChange(&le->as, le->model1, LE_GetAnim("walk", le->right, le->left, le->state)); if (!le->as.change) Com_Printf("LET_StartPathMove: Could not change anim of le: %i, team: %i, pnum: %i\n", le->entnum, le->team, le->pnum); LE_SetThink(le, LET_PathMove); LE_ExecuteThink(le); }
/** * @brief Rotates a door in the given speed * * @param[in] le The local entity of the door to rotate * @param[in] speed The speed to rotate the door with */ void LET_RotateDoor (le_t* le, int speed) { /** @todo lerp the rotation */ const int angle = speed > 0 ? DOOR_ROTATION_ANGLE : -DOOR_ROTATION_ANGLE; if (le->dir & DOOR_OPEN_REVERSE) le->angles[le->dir & 3] -= angle; else le->angles[le->dir & 3] += angle; CM_SetInlineModelOrientation(cl.mapTiles, le->inlineModelName, le->origin, le->angles); CL_RecalcRouting(le); /* reset the think function as the movement finished */ LE_SetThink(le, nullptr); }
/** * @sa CL_InvAdd */ void CL_InvDel (const eventRegister_t *self, dbuffer *msg) { le_t *le; int number; int x, y; containerIndex_t container; invList_t *ic; NET_ReadFormat(msg, self->formatString, &number, &container, &x, &y); le = LE_Get(number); if (!le) Com_Error(ERR_DROP, "InvDel message ignored... LE not found\n"); /* update the local entity to ensure that the correct weapon/item is rendered in the battlescape */ if (container == csi.idRight) le->right = NONE; else if (container == csi.idLeft) le->left = NONE; else if (container == csi.idExtension) le->extension = NONE; else if (container == csi.idHeadgear) le->headgear = NONE; if (le->type == ET_ACTOR || le->type == ET_ACTOR2x2) LE_SetThink(le, LET_StartIdle); ic = INVSH_SearchInInventory(&le->i, INVDEF(container), x, y); /* ic can be null for other team actors - we don't the full inventory of them, only * the object index */ if (!ic) return; if (!cls.i.RemoveFromInventory(&cls.i, &le->i, INVDEF(container), ic)) Com_Error(ERR_DROP, "CL_InvDel: No item was removed from container %i", container); if (le == selActor) Cmd_ExecuteString("hud_updateactorload"); /* update the rendered item after it was removed from the floor container */ if (LE_IsItem(le)) LE_PlaceItem(le); }
/** * @brief Ends the move of an actor */ void LE_DoEndPathMove (le_t* le) { /* Verify the current position */ if (!VectorCompare(le->pos, le->newPos)) Com_Error(ERR_DROP, "LE_DoEndPathMove: Actor movement is out of sync: %i:%i:%i should be %i:%i:%i (step %i of %i) (team %i)", le->pos[0], le->pos[1], le->pos[2], le->newPos[0], le->newPos[1], le->newPos[2], le->pathPos, le->pathLength, le->team); CL_ActorConditionalMoveCalc(le); /* if the moving actor was not the selected one, */ /* recalc the pathing table for the selected one, too. */ if (!LE_IsSelected(le)) { CL_ActorConditionalMoveCalc(selActor); } LE_LinkFloorContainer(le); LE_SetThink(le, LET_StartIdle); LE_ExecuteThink(le); LE_Unlock(le); }
/** * @param[in] fd The grenade fire definition * @param[in] flags bitmask: SF_BODY, SF_IMPACT, SF_BOUNCING, SF_BOUNCED * @param[in] muzzle starting/location vector * @param[in] v0 velocity vector * @param[in] dt delta seconds * @param[in] leVictim The actor the grenade is thrown at (not yet supported) */ void LE_AddGrenade (const fireDef_t* fd, int flags, const vec3_t muzzle, const vec3_t v0, int dt, le_t* leVictim) { /* add le */ le_t* le = LE_Add(0); if (!le) return; LE_SetInvisible(le); /* bind particle */ vec3_t accel; VectorSet(accel, 0, 0, -GRAVITY); le->ptl = CL_ParticleSpawn(fd->projectile, 0, muzzle, v0, accel); if (!le->ptl) { le->inuse = false; return; } /* particle properties */ VectorSet(le->ptl->angles, 360 * crand(), 360 * crand(), 360 * crand()); VectorSet(le->ptl->omega, 500 * crand(), 500 * crand(), 500 * crand()); /* think function */ if (flags & SF_BODY) { le->ref1 = fd->hitBody; le->ref2 = fd->hitBodySound; le->ref3 = leVictim; } else if ((flags & SF_IMPACT) || (fd->splrad && !fd->bounce)) { le->ref1 = fd->impact; le->ref2 = fd->impactSound; } else { le->ref1 = nullptr; if (flags & SF_BOUNCING) le->ref2 = fd->bounceSound; } le->endTime = cl.time + dt; /* direction - bytedirs index (0,0,1) */ le->angle = 5; le->fd = fd; LE_SetThink(le, LET_Projectile); LE_ExecuteThink(le); }
/** * @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); }
/** * @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); }
void LE_AddProjectile (const fireDef_t* fd, int flags, const vec3_t muzzle, const vec3_t impact, int normal, le_t* leVictim) { /* add le */ le_t* le = LE_Add(0); if (!le) return; LE_SetInvisible(le); /* bind particle */ le->ptl = CL_ParticleSpawn(fd->projectile, 0, muzzle); if (!le->ptl) { le->inuse = false; return; } /* calculate parameters */ vec3_t delta; VectorSubtract(impact, muzzle, delta); const float dist = VectorLength(delta); VecToAngles(delta, le->ptl->angles); /* direction - bytedirs index */ le->angle = normal; le->fd = fd; /* infinite speed projectile? */ if (!fd->speed) { le->inuse = false; le->ptl->size[0] = dist; VectorMA(muzzle, 0.5, delta, le->ptl->s); if ((flags & (SF_IMPACT | SF_BODY)) || (fd->splrad && !fd->bounce)) { ptl_t* ptl = nullptr; const float* dir = bytedirs[le->angle]; if (flags & SF_BODY) { if (fd->hitBodySound != nullptr) { S_LoadAndPlaySample(fd->hitBodySound, le->origin, le->fd->impactAttenuation, SND_VOLUME_WEAPONS); } if (fd->hitBody != nullptr) ptl = CL_ParticleSpawn(fd->hitBody, 0, impact, dir); /* Spawn blood particles (if defined) if actor(-body) was hit. Even if actor is dead. */ /** @todo Special particles for stun attack (mind you that there is * electrical and gas/chemical stunning)? */ if (leVictim) { if (fd->obj->dmgtype != csi.damStunGas) LE_ActorBodyHit(leVictim, impact, le->angle); if (fd->damage[0] >= 0) CL_ActorPlaySound(leVictim, SND_HURT); } } else { if (fd->impactSound != nullptr) { S_LoadAndPlaySample(fd->impactSound, le->origin, le->fd->impactAttenuation, SND_VOLUME_WEAPONS); } if (fd->impact != nullptr) ptl = CL_ParticleSpawn(fd->impact, 0, impact, dir); } if (ptl) VecToAngles(dir, ptl->angles); } return; } /* particle properties */ VectorScale(delta, fd->speed / dist, le->ptl->v); le->endTime = cl.time + 1000 * dist / fd->speed; /* think function */ if (flags & SF_BODY) { le->ref1 = fd->hitBody; le->ref2 = fd->hitBodySound; le->ref3 = leVictim; } else if ((flags & SF_IMPACT) || (fd->splrad && !fd->bounce)) { le->ref1 = fd->impact; le->ref2 = fd->impactSound; } else { le->ref1 = nullptr; if (flags & SF_BOUNCING) le->ref2 = fd->bounceSound; } LE_SetThink(le, LET_Projectile); LE_ExecuteThink(le); }
/** * @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 Register local entities for SOLID_BSP models like func_breakable or func_door * @note func_breakable, func_door * @sa G_SendEdictsAndBrushModels * @sa EV_ADD_BRUSH_MODEL * @sa CL_SpawnParseEntitystring */ void CL_AddBrushModel (const eventRegister_t *self, struct dbuffer *msg) { le_t *le; int entnum, modelnum1, levelflags, speed, dir; entity_type_t type; const cBspModel_t *model; int angle; vec3_t origin, angles; NET_ReadFormat(msg, self->formatString, &type, &entnum, &modelnum1, &levelflags, &origin, &angles, &speed, &angle, &dir); if (type != ET_BREAKABLE && type != ET_DOOR && type != ET_ROTATING && type != ET_DOOR_SLIDING && type != ET_TRIGGER_RESCUE && type != ET_TRIGGER_NEXTMAP) Com_Error(ERR_DROP, "Invalid le announced via EV_ADD_BRUSH_MODEL type: %i\n", type); else if (modelnum1 > MAX_MODELS || modelnum1 < 1) Com_Error(ERR_DROP, "Invalid le modelnum1 announced via EV_ADD_BRUSH_MODEL\n"); /* check if the ent is already visible */ le = LE_Get(entnum); if (le) Com_Error(ERR_DROP, "le announced a second time - le for entnum %i (type: %i) already exists (via EV_ADD_BRUSH_MODEL)\n", entnum, type); le = LE_Add(entnum); assert(le); le->rotationSpeed = speed / 100.0f; le->slidingSpeed = speed; le->angle = angle; le->dir = dir; le->type = type; le->modelnum1 = modelnum1; le->levelflags = levelflags; le->addFunc = LE_BrushModelAction; LE_SetThink(le, LET_BrushModel); /* The origin and angles are REQUIRED for doors to work! */ VectorCopy(origin, le->origin); /* store the initial position - needed for sliding doors */ VectorCopy(le->origin, le->oldOrigin); VectorCopy(angles, le->angles); Com_sprintf(le->inlineModelName, sizeof(le->inlineModelName), "*%i", le->modelnum1); model = LE_GetClipModel(le); le->model1 = R_FindModel(le->inlineModelName); if (!le->model1) Com_Error(ERR_DROP, "CL_AddBrushModel: Could not register inline model %i", le->modelnum1); /* Transfer model mins and maxs to entity */ VectorCopy(model->mins, le->mins); VectorCopy(model->maxs, le->maxs); VectorSubtract(le->maxs, le->mins, le->size); VecToPos(le->origin, le->pos); /* to allow tracing against this le */ if (!LE_IsNotSolid(le)) { /* This is to help the entity collision code out */ /* Copy entity origin and angles to model*/ CM_SetInlineModelOrientation(cl.mapTiles, le->inlineModelName, le->origin, le->angles); le->contents = CONTENTS_SOLID; CL_RecalcRouting(le); } }