void G_EventSendState (unsigned int playerMask, const edict_t *ent) { G_EventActorStateChange(playerMask & G_TeamToPM(ent->team), ent); G_EventAdd(playerMask & ~G_TeamToPM(ent->team), EV_ACTOR_STATECHANGE, ent->number); gi.WriteShort(ent->state & STATE_PUBLIC); G_EventEnd(); }
void G_EventSendState (playermask_t playerMask, const Edict& ent) { G_EventActorStateChange(playerMask & G_TeamToPM(ent.team), ent); G_EventAdd(playerMask & ~G_TeamToPM(ent.team), EV_ACTOR_STATECHANGE, ent.number); gi.WriteShort(ent.state & STATE_PUBLIC); G_EventEnd(); }
/** * @brief Informs the client that an interaction with the world is possible * @note It's assumed that the clientAction is already set * @param[in] ent The edict that can execute the action (an actor) */ void G_EventSetClientAction (const Edict& ent) { assert(ent.clientAction); assert(ent.clientAction->flags & FL_CLIENTACTION); /* tell the hud to show the door buttons */ G_EventAdd(G_TeamToPM(ent.team), EV_CLIENT_ACTION, ent.number); gi.WriteShort(ent.clientAction->number); G_EventEnd(); }
/** * @brief Send stats to network buffer * @sa CL_ActorStats */ void G_SendStats (Edict& ent) { /* extra sanity checks */ assert(ent.TU >= 0); ent.HP = std::max(ent.HP, 0); ent.STUN = std::min(ent.STUN, 255); ent.morale = std::max(ent.morale, 0); G_EventActorStats(ent, G_TeamToPM(ent.team)); }
/** * @brief Guess! Reset all "shaken" states on end of turn? * @note Normally called on end of turn. * @sa G_ClientStateChange * @param[in] team Index of team to loop through. */ void G_ReactionFireReset (int team) { edict_t *ent = NULL; while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team))) { /** @todo why do we send the state here and why do we change the "shaken" * state? - see G_MoraleBehaviour */ G_RemoveShaken(ent); G_EventActorStateChange(G_TeamToPM(ent->team), ent); } }
/** * @brief Checks whether an edict appear/perishes for a specific team - also * updates the visflags each edict carries * @sa G_TestVis * @param[in] team Team to check the vis for * @param[in] check The edict that you want to check (and which maybe will appear * or perish for the given team). If this is NULL every edict will be checked. * @param[in] perish Also check whether the edict (the actor) is going to become * invisible for the given team * @param[in] ent The edict that is (maybe) seeing other edicts * @return If an actor get visible who's no civilian VIS_STOP is added to the * bit mask, VIS_YES means, he is visible, VIS_CHANGE means that the actor * flipped its visibility (invisible to visible or vice versa), VIS_PERISH means * that the actor (the edict) is getting invisible for the queried team * @note the edict may not only be actors, but also items of course * @sa G_TeamToPM * @sa G_TestVis * @sa G_AppearPerishEvent * @sa G_CheckVisPlayer * @sa G_CheckVisTeamAll * @note If something appears, the needed information for those clients that are affected * are also send in @c G_AppearPerishEvent */ int G_CheckVisTeam (const int team, edict_t *check, bool perish, const edict_t *ent) { int status = 0; /* check visibility */ if (check->inuse) { /* check if he's visible */ status |= G_DoTestVis(team, check, perish, G_TeamToPM(team), ent); } return status; }
/** * @brief Checks whether an edict appear/perishes for a specific team - also * updates the visflags each edict carries * @sa G_TestVis * @param[in] team Team to check the vis for * @param[in] check The edict that you want to check (and which maybe will appear * or perish for the given team). If this is nullptr every edict will be checked. * @param visFlags Modifiers for the checks * @param[in] ent The edict that is (maybe) seeing other edicts * @return If an actor get visible who's no civilian VIS_STOP is added to the * bit mask, VIS_APPEAR means, that the actor is becoming visible for the queried * team, VIS_PERISH means that the actor (the edict) is getting invisible * @note the edict may not only be actors, but also items of course * @sa G_TeamToPM * @sa G_TestVis * @sa G_AppearPerishEvent * @sa G_CheckVisPlayer * @sa G_CheckVisTeamAll * @note If something appears, the needed information for those clients that are affected * are also send in @c G_AppearPerishEvent */ static int G_CheckVisTeam (const int team, Edict* check, const vischeckflags_t visFlags, const Edict* ent) { int status = 0; /* check visibility */ if (check->inuse) { /* check if he's visible */ status |= G_DoTestVis(team, check, visFlags, G_TeamToPM(team), ent); } return status; }
void G_EventActorAppear (playermask_t playerMask, const Edict& check, const Edict* ent) { const int mask = G_TeamToPM(check.team) & playerMask; /* parsed in CL_ActorAppear */ G_EventAdd(playerMask, EV_ACTOR_APPEAR, check.number); gi.WriteShort(ent && ent->number > 0 ? ent->number : SKIP_LOCAL_ENTITY); gi.WriteByte(check.team); gi.WriteByte(check.chr.teamDef ? check.chr.teamDef->idx : NONE); gi.WriteByte(check.chr.gender); gi.WriteShort(check.chr.ucn); gi.WriteByte(check.pnum); gi.WriteGPos(check.pos); gi.WriteByte(check.dir); if (check.getRightHandItem()) { gi.WriteShort(check.getRightHandItem()->def()->idx); } else { gi.WriteShort(NONE); } if (check.getLeftHandItem()) { gi.WriteShort(check.getLeftHandItem()->def()->idx); } else { gi.WriteShort(NONE); } if (check.body == 0 || check.head == 0) { gi.Error("invalid body and/or head model indices"); } gi.WriteShort(check.body); gi.WriteShort(check.head); gi.WriteByte(check.chr.bodySkin); gi.WriteByte(check.chr.headSkin); /* strip the server private states */ gi.WriteShort(check.state & STATE_PUBLIC); gi.WriteByte(check.fieldSize); /* get the max values for TU and morale */ gi.WriteByte(G_ActorCalculateMaxTU(&check)); gi.WriteByte(std::min(MAX_SKILL, GET_MORALE(check.chr.score.skills[ABILITY_MIND]))); gi.WriteShort(check.chr.maxHP); G_EventEnd(); if (mask) { G_EventActorStateChange(mask, check); G_SendInventory(mask, check); } }
/** * @brief Deals splash damage to a target and its surroundings. * @param[in] ent The shooting actor * @param[in] fd The fire definition that defines what type of damage is dealt and how big the splash radius is. * @param[in] impact The impact vector where the grenade is exploding * @param[in,out] mock pseudo shooting - only for calculating mock values - NULL for real shots * @param[in] tr The trace where the grenade hits something (or not) */ static void G_SplashDamage (edict_t *ent, const fireDef_t *fd, vec3_t impact, shot_mock_t *mock, const trace_t* tr) { edict_t *check = NULL; vec3_t center; float dist; int damage; const bool shock = (fd->obj->dmgtype == gi.csi->damShock); assert(fd->splrad > 0.0); while ((check = G_EdictsGetNextInUse(check))) { /* If we use a blinding weapon we skip the target if it's looking * away from the impact location. */ if (shock && !G_FrustumVis(check, impact)) continue; if (G_IsBrushModel(check) && G_IsBreakable(check)) VectorCenterFromMinsMaxs(check->absmin, check->absmax, center); else if (G_IsLivingActor(check) || G_IsBreakable(check)) VectorCopy(check->origin, center); else continue; /* check for distance */ dist = VectorDist(impact, center); dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0; if (dist > fd->splrad) continue; if (fd->irgoggles) { if (G_IsActor(check)) { /* check whether this actor (check) is in the field of view of the 'shooter' (ent) */ if (G_FrustumVis(ent, check->origin)) { if (!mock) { const unsigned int playerMask = G_TeamToPM(ent->team) ^ G_VisToPM(check->visflags); G_AppearPerishEvent(playerMask, true, check, ent); G_VisFlagsAdd(check, G_PMToVis(playerMask)); } } } continue; } /* check for walls */ if (G_IsLivingActor(check) && !G_ActorVis(impact, ent, check, false)) continue; /* do damage */ if (shock) damage = 0; else damage = fd->spldmg[0] * (1.0 - dist / fd->splrad); if (mock) mock->allow_self = true; G_Damage(check, fd, damage, ent, mock, NULL); if (mock) mock->allow_self = false; } /** @todo splash might also hit other surfaces and the trace doesn't handle that */ if (tr && G_FireAffectedSurface(tr->surface, fd)) { /* move a little away from the impact vector */ VectorMA(impact, 1, tr->plane.normal, impact); G_SpawnParticle(impact, tr->contentFlags >> 8, "burning"); }
/** * @brief Moves an item inside an inventory. Floors are handled special. * @param[in] actor The pointer to the selected/used edict/soldier. * @param[in] fromContType The container (-id) the item should be moved from. * @param[in] fItem The item you want to move. * @param[in] toContType The container (-def) the item should be moved to. * @param[in] tx x position where you want the item to go in the destination container * @param[in] ty y position where you want the item to go in the destination container * @param[in] checkaction Set this to true if you want to check for TUs, otherwise false. * @sa event PA_INVMOVE * @sa AI_ActorThink */ bool G_ActorInvMove (Edict* actor, const invDef_t* fromContType, Item* fItem, const invDef_t* toContType, int tx, int ty, bool checkaction) { Edict* floor; bool newFloor; Item* tc; playermask_t mask; inventory_action_t ia; Item fromItemBackup, toItemBackup; int fx, fy; int originalTU, reservedTU = 0; Player& player = actor->getPlayer(); assert(fItem); assert(fItem->def()); /* Store the location/item of 'from' BEFORE actually moving items with moveInInventory. */ fromItemBackup = *fItem; /* Store the location of 'to' BEFORE actually moving items with moveInInventory * so in case we swap ammo the client can be updated correctly */ tc = actor->chr.inv.getItemAtPos(toContType, tx, ty); if (tc) toItemBackup = *tc; else toItemBackup = *fItem; /* Get first used bit in item. */ fItem->getFirstShapePosition(&fx, &fy); fx += fItem->getX(); fy += fItem->getY(); /* Check if action is possible */ /* TUs are 1 here - but this is only a dummy - the real TU check is done in the inventory functions below */ if (checkaction && !G_ActionCheckForCurrentTeam(player, actor, 1)) return false; if (!actor->chr.inv.canHoldItemWeight(fromContType->id, toContType->id, *fItem, actor->chr.score.skills[ABILITY_POWER])) { G_ClientPrintf(player, PRINT_HUD, _("This soldier can not carry anything else.")); return false; } /* "get floor ready" - searching for existing floor-edict */ floor = G_GetFloorItems(actor); if (toContType->isFloorDef() && !floor) { /* We are moving to the floor, but no existing edict for this floor-tile found -> create new one */ floor = G_SpawnFloor(actor->pos); newFloor = true; } else if (fromContType->isFloorDef() && !floor) { /* We are moving from the floor, but no existing edict for this floor-tile found -> this should never be the case. */ gi.DPrintf("G_ClientInvMove: No source-floor found.\n"); return false; } else { /* There already exists an edict for this floor-tile. */ newFloor = false; } /* search for space */ Item* item2; if (tx == NONE) { item2 = actor->chr.inv.getItemAtPos(fromContType, fItem->getX(), fItem->getY()); if (item2) actor->chr.inv.findSpace(toContType, item2, &tx, &ty, fItem); if (tx == NONE) return false; } /** @todo what if we don't have enough TUs after subtracting the reserved ones? */ /* Because moveInInventory don't know anything about character_t and it updates actor->TU, * we need to save original actor->TU for the sake of checking TU reservations. */ originalTU = actor->TU; reservedTU = G_ActorGetReservedTUs(actor); /* Temporary decrease actor->TU to make moveInInventory do what expected. */ G_ActorUseTU(actor, reservedTU); /* Try to actually move the item and check the return value after restoring valid actor->TU. */ ia = game.i.moveInInventory(&actor->chr.inv, fromContType, fItem, toContType, tx, ty, checkaction ? &actor->TU : nullptr, &item2); /* Now restore the original actor->TU and decrease it for TU used for inventory move. */ G_ActorSetTU(actor, originalTU - (originalTU - reservedTU - actor->TU)); switch (ia) { case IA_NONE: /* No action possible - abort */ return false; case IA_NOTIME: G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - not enough TUs!")); return false; case IA_NORELOAD: G_ClientPrintf(player, PRINT_HUD, _("Can't perform action - weapon already fully loaded with the same ammunition!")); return false; default: /* Continue below. */ break; } /* successful inventory change; remove the item in clients */ if (fromContType->isFloorDef()) { /* We removed an item from the floor - check how the client * needs to be updated. */ assert(!newFloor); if (actor->getFloor()) { /* There is still something on the floor. */ floor->setFloor(actor); /* Delay this if swapping ammo, otherwise the le will be removed in the client before we can add back * the current ammo because removeNextFrame is set in LE_PlaceItem() if the floor le has no items */ if (ia != IA_RELOAD_SWAP) G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } else { /* Floor is empty, remove the edict (from server + client) if we are * not moving to it. */ if (!toContType->isFloorDef()) { G_EventPerish(*floor); G_FreeEdict(floor); } else G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } } else { G_EventInventoryDelete(*actor, G_TeamToPM(actor->team), fromContType->id, fx, fy); } /* send tu's */ G_SendStats(*actor); assert(item2); Item item = *item2; if (ia == IA_RELOAD || ia == IA_RELOAD_SWAP) { /* reload */ if (toContType->isFloorDef()) mask = G_VisToPM(floor->visflags); else mask = G_TeamToPM(actor->team); G_EventInventoryReload(toContType->isFloorDef() ? *floor : *actor, mask, &item, toContType, item2); if (ia == IA_RELOAD) { return true; } else { /* ia == IA_RELOAD_SWAP */ item.setAmmoLeft(NONE_AMMO); item.setAmmoDef(nullptr); item.setDef(toItemBackup.ammoDef()); item.rotated = fromItemBackup.rotated; item.setAmount(toItemBackup.getAmount()); toContType = fromContType; if (toContType->isFloorDef()) { /* moveInInventory placed the swapped ammo in an available space, check where it was placed * so we can place it at the same place in the client, otherwise since fItem hasn't been removed * this could end in a different place in the client - will cause an error if trying to use it again */ item2 = actor->chr.inv.findInContainer(toContType->id, &item); assert(item2); fromItemBackup = item; fromItemBackup.setX(item2->getX()); fromItemBackup.setY(item2->getY()); } tx = fromItemBackup.getX(); ty = fromItemBackup.getY(); } } /* We moved an item to the floor - check how the client needs to be updated. */ if (toContType->isFloorDef()) { /* we have to link the temp floor container to the new floor edict or add * the item to an already existing floor edict - the floor container that * is already linked might be from a different entity (this might happen * in case of a throw by another actor) */ floor->setFloor(actor); /* A new container was created for the floor. */ if (newFloor) { /* Send item info to the clients */ G_CheckVis(floor); } else { /* use the backup item to use the old amount values, because the clients have to use the same actions * on the original amount. Otherwise they would end in a different amount of items as the server (+1) */ G_EventInventoryAdd(*floor, G_VisToPM(floor->visflags), 1); G_WriteItem(fromItemBackup, toContType->id, tx, ty); G_EventEnd(); /* Couldn't remove it before because that would remove the le from the client and would cause battlescape to crash * when trying to add back the swapped ammo above */ if (ia == IA_RELOAD_SWAP) G_EventInventoryDelete(*floor, G_VisToPM(floor->visflags), fromContType->id, fx, fy); } } else { G_EventInventoryAdd(*actor, G_TeamToPM(actor->team), 1); G_WriteItem(item, toContType->id, tx, ty); G_EventEnd(); } G_ReactionFireSettingsUpdate(actor, actor->chr.RFmode.getFmIdx(), actor->chr.RFmode.getHand(), actor->chr.RFmode.getWeapon()); /* Other players receive weapon info only. */ mask = G_VisToPM(actor->visflags) & ~G_TeamToPM(actor->team); if (mask) { if (fromContType->isRightDef() || fromContType->isLeftDef()) { G_EventInventoryDelete(*actor, mask, fromContType->id, fx, fy); } if (toContType->isRightDef() || toContType->isLeftDef()) { G_EventInventoryAdd(*actor, mask, 1); G_WriteItem(item, toContType->id, tx, ty); G_EventEnd(); } } return true; }
/** * @brief Deals splash damage to a target and its surroundings. * @param[in] ent The shooting actor * @param[in] fd The fire definition that defines what type of damage is dealt and how big the splash radius is. * @param[in] impact The impact vector where the grenade is exploding * @param[in,out] mock pseudo shooting - only for calculating mock values - nullptr for real shots * @param[in] tr The trace where the grenade hits something (or not) */ static void G_SplashDamage (Actor* ent, const fireDef_t* fd, vec3_t impact, shot_mock_t* mock, const trace_t* tr) { assert(fd->splrad > 0.0f); const bool shock = (fd->obj->dmgtype == gi.csi->damShock); Edict* check = nullptr; while ((check = G_EdictsGetNextInUse(check))) { /* If we use a blinding weapon we skip the target if it's looking * away from the impact location. */ if (shock && !G_FrustumVis(check, impact)) continue; const bool isActor = G_IsLivingActor(check); vec3_t center; if (G_IsBrushModel(check) && G_IsBreakable(check)) check->absBox.getCenter(center); else if (isActor || G_IsBreakable(check)) VectorCopy(check->origin, center); else continue; /* check for distance */ float dist = VectorDist(impact, center); dist = dist > UNIT_SIZE / 2 ? dist - UNIT_SIZE / 2 : 0.0f; if (dist > fd->splrad) continue; if (fd->irgoggles) { if (isActor) { /* check whether this actor (check) is in the field of view of the 'shooter' (ent) */ if (G_FrustumVis(ent, check->origin)) { if (!mock) { vec3_t eyeEnt; G_ActorGetEyeVector(ent, eyeEnt); if (!G_SmokeVis(eyeEnt, check)) { const unsigned int playerMask = G_TeamToPM(ent->getTeam()) ^ G_VisToPM(check->visflags); G_AppearPerishEvent(playerMask, true, *check, ent); G_VisFlagsAdd(*check, G_PMToVis(playerMask)); } } } } continue; } /* check for walls */ if (isActor && G_TestLine(impact, check->origin)) continue; /* do damage */ const int damage = shock ? 0 : fd->spldmg[0] * (1.0f - dist / fd->splrad); if (mock) mock->allow_self = true; /* Send hurt sounds for actors, but only if they'll recieve damage from this attack */ if (G_Damage(check, fd, damage, ent, mock, nullptr) && isActor && (G_ApplyProtection(check, fd->dmgweight, damage) > 0) && !shock) { const teamDef_t* teamDef = check->chr.teamDef; const int gender = check->chr.gender; const char* sound = teamDef->getActorSound(gender, SND_HURT); G_EventSpawnSound(G_VisToPM(check->visflags), *check, nullptr, sound); } if (mock) mock->allow_self = false; } /** @todo splash might also hit other surfaces and the trace doesn't handle that */ if (tr && G_FireAffectedSurface(tr->surface, fd)) { /* move a little away from the impact vector */ VectorMA(impact, 1, tr->plane.normal, impact); G_SpawnParticle(impact, tr->contentFlags >> 8, "burning"); }