static void G_UseEdict_f (void) { Edict* e; int i; if (gi.Cmd_Argc() < 2) { gi.DPrintf("Usage: %s <entnum>\n", gi.Cmd_Argv(0)); return; } i = atoi(gi.Cmd_Argv(1)); if (!G_EdictsIsValidNum(i)) { gi.DPrintf("No entity with number %i\n", i); return; } e = G_EdictsGetByNum(i); if (!e->use) { gi.DPrintf("No use function for entity %s\n", e->classname); return; } gi.DPrintf("Call use function for %s\n", e->classname); e->use(e, nullptr); }
void G_EventReactionFireTargetUpdate (const Edict& shooter, const Edict& target, int tus, int step) { gi.QueueEvent(G_PlayerToPM(shooter.getPlayer()), EV_ACTOR_REACTIONFIRETARGETUPDATE, shooter.getIdNum()); gi.QueueWriteShort(target.getIdNum()); gi.QueueWriteByte(tus); gi.QueueWriteByte(step); }
void G_EventSendState (playermask_t playerMask, const Edict& ent) { G_EventActorStateChange(playerMask & G_TeamToPM(ent.getTeam()), ent); G_EventAdd(playerMask & ~G_TeamToPM(ent.getTeam()), EV_ACTOR_STATECHANGE, ent.getIdNum()); gi.WriteShort(ent.state & STATE_PUBLIC); G_EventEnd(); }
/** * @brief Announce the actor die event for the clients that are seeing the actor * @param[in] ent The actor that is dying * @param[in] attacker Whether the actor was killed or just died */ void G_EventActorDie (const Edict& ent, bool attacker) { G_EventAdd(G_VisToPM(ent.visflags), EV_ACTOR_DIE, ent.getIdNum()); gi.WriteShort(ent.state); gi.WriteByte(ent.getPlayerNum()); gi.WriteByte(attacker); G_EventEnd(); }
void G_EventActorStats (const Edict& ent, playermask_t playerMask) { G_EventAdd(playerMask, EV_ACTOR_STATS, ent.getIdNum()); gi.WriteByte(ent.TU); gi.WriteShort(ent.HP); gi.WriteByte(ent.getStun()); gi.WriteByte(ent.morale); G_EventEnd(); }
TEST_F(GameTest, InventoryForDiedAlien) { const char* mapName = "test_game"; ASSERT_NE(-1, FS_CheckFile("maps/%s.bsp", mapName)) << "Map resource '" << mapName << ".bsp' for test is missing."; Actor* diedEnt; Actor* actor; Edict* floorItems; Item* invlist; int count; SV_Map(true, mapName, nullptr); level.activeTeam = TEAM_ALIEN; /* first alien that should die and drop its inventory */ diedEnt = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN); ASSERT_TRUE(nullptr != diedEnt); diedEnt->HP = 0; ASSERT_TRUE(G_ActorDieOrStun(diedEnt, nullptr)); ASSERT_TRUE(diedEnt->isDead()); /* now try to collect the inventory with a second alien */ actor = G_EdictsGetNextLivingActorOfTeam(nullptr, TEAM_ALIEN); ASSERT_TRUE(nullptr != actor); /* move to the location of the first died alien to drop the inventory into the same floor container */ Player& player = actor->getPlayer(); ASSERT_TRUE(G_IsAIPlayer(&player)); G_ClientMove(player, 0, actor, diedEnt->pos); ASSERT_TRUE(VectorCompare(actor->pos, diedEnt->pos)); floorItems = G_GetFloorItems(actor); ASSERT_TRUE(nullptr != floorItems); ASSERT_EQ(floorItems->getFloor(), actor->getFloor()); /* drop everything to floor to make sure we have space in the backpack */ G_InventoryToFloor(actor); ASSERT_EQ(0, GAMETEST_GetItemCount(actor, CID_BACKPACK)); invlist = actor->getContainer(CID_BACKPACK); ASSERT_TRUE(nullptr == invlist); count = GAMETEST_GetItemCount(actor, CID_FLOOR); if (count > 0) { Item* entryToMove = actor->getFloor(); int tx, ty; actor->chr.inv.findSpace(INVDEF(CID_BACKPACK), entryToMove, &tx, &ty, entryToMove); if (tx == NONE) return; Com_Printf("trying to move item %s from floor into backpack to pos %i:%i\n", entryToMove->def()->name, tx, ty); ASSERT_TRUE(G_ActorInvMove(actor, INVDEF(CID_FLOOR), entryToMove, INVDEF(CID_BACKPACK), tx, ty, false)); ASSERT_EQ(count - 1, GAMETEST_GetItemCount(actor, CID_FLOOR)) << "item " << entryToMove->def()->name << " could not get moved successfully from floor into backpack"; Com_Printf("item %s was removed from floor\n", entryToMove->def()->name); ASSERT_EQ(1, GAMETEST_GetItemCount(actor, CID_BACKPACK)) << "item " << entryToMove->def()->name << " could not get moved successfully from floor into backpack"; Com_Printf("item %s was moved successfully into the backpack\n", entryToMove->def()->name); invlist = actor->getContainer(CID_BACKPACK); ASSERT_TRUE(nullptr != invlist); } }
/** * @brief Make everything visible to anyone who can't already see it */ void G_VisMakeEverythingVisible (void) { Edict* ent = nullptr; while ((ent = G_EdictsGetNextInUse(ent))) { const int playerMask = G_VisToPM(ent->visflags); G_AppearPerishEvent(~playerMask, true, *ent, nullptr); if (G_IsActor(ent)) G_SendInventory(~G_TeamToPM(ent->getTeam()), *ent); } }
/** * @brief Send info about an actor's wounds to the client. * @param[in] ent The actor whose wound status we are sending. * @param[in] bodyPart The body part index we are sending wound info about. * @note This event is sent to the player this actor belongs to */ void G_EventActorWound (const Edict& ent, const int bodyPart) { const int mask = G_PlayerToPM(ent.getPlayer()); G_EventAdd(mask, EV_ACTOR_WOUND, ent.getIdNum()); gi.WriteByte(bodyPart); const woundInfo_t& wounds = ent.chr.wounds; gi.WriteByte(wounds.woundLevel[bodyPart]); gi.WriteByte(wounds.treatmentLevel[bodyPart]); 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.getTeam()), EV_CLIENT_ACTION, ent.getIdNum()); gi.WriteShort(ent.clientAction->getIdNum()); G_EventEnd(); }
/** * @brief Will inform the player about the real TU reservation * @param ent The actors edict. */ void G_EventActorSendReservations (const Edict& ent) { G_EventAdd(G_PlayerToPM(ent.getPlayer()), EV_ACTOR_RESERVATIONCHANGE, ent.getIdNum()); const chrReservations_t& reservedTUs = ent.chr.reservedTus; gi.WriteShort(reservedTUs.reaction); gi.WriteShort(reservedTUs.shot); gi.WriteShort(reservedTUs.crouch); G_EventEnd(); }
/** * @brief Applies morale behaviour on actors * @note only called when mor_panic is not zero * @sa G_MoralePanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleStopPanic */ void G_MoraleBehaviour (int team) { bool enabled = G_IsMoraleEnabled(team); if (!enabled) return; Edict* ent = nullptr; while ((ent = G_EdictsGetNextLivingActorOfTeam(ent, team)) != nullptr) { /* this only applies to ET_ACTOR but not to ET_ACTOR2x2 */ if (ent->type != ET_ACTOR || CHRSH_IsTeamDefRobot(ent->chr.teamDef)) continue; /* if panic, determine what kind of panic happens: */ if (!G_IsPanicked(ent) && !G_IsRaged(ent)) { if (ent->morale <= mor_panic->integer) { const float ratio = (float) ent->morale / mor_panic->value; const bool sanity = ratio > (m_sanity->value * frand()); if (!sanity) G_SetInsane(ent); if (ratio > (m_rage->value * frand())) G_MoralePanic(ent); else G_MoraleRage(ent); /* if shaken, well .. be shaken; */ } else if (ent->morale <= mor_shaken->integer) { /* shaken is later reset along with reaction fire */ G_SetShaken(ent); G_ClientStateChange(ent->getPlayer(), ent, STATE_REACTION, false); G_EventSendState(G_VisToPM(ent->visflags), *ent); G_ClientPrintf(ent->getPlayer(), PRINT_HUD, _("%s is currently shaken."), ent->chr.name); G_PrintStats("%s is shaken (entnum %i).", ent->chr.name, ent->getIdNum()); } } else { if (G_IsPanicked(ent)) G_MoraleStopPanic(ent); else if (G_IsRaged(ent)) G_MoraleStopRage(ent); } G_ActorSetMaxs(ent); /* morale-regeneration, capped at max: */ int newMorale = ent->morale + MORALE_RANDOM(mor_regeneration->value); const int maxMorale = GET_MORALE(ent->chr.score.skills[ABILITY_MIND]); if (newMorale > maxMorale) ent->morale = maxMorale; else ent->morale = newMorale; /* send phys data and state: */ G_SendStats(*ent); } }
void G_EventReactionFireChange (const Edict& ent) { const FiremodeSettings& fireMode = ent.chr.RFmode; const objDef_t* od = fireMode.getWeapon(); G_EventAdd(G_PlayerToPM(ent.getPlayer()), EV_ACTOR_REACTIONFIRECHANGE, ent.getIdNum()); gi.WriteByte(fireMode.getFmIdx()); gi.WriteByte(fireMode.getHand()); gi.WriteShort(od ? od->idx : NONE); G_EventEnd(); }
/** * @brief Send an appear event to the client. * @param playerMask The players to send the event to * @param ent The camera that should appear to the players included in the given mask. */ void G_EventCameraAppear (playermask_t playerMask, const Edict& ent) { G_EventAdd(playerMask, EV_CAMERA_APPEAR, ent.getIdNum()); gi.WritePos(ent.origin); gi.WriteByte(ent.getTeam()); gi.WriteByte(ent.dir); gi.WriteByte(ent.camera.cameraType); /* strip the higher bits - only send levelflags */ gi.WriteByte(ent.spawnflags & 0xFF); gi.WriteByte(ent.camera.rotate); G_EventEnd(); }
/** * @sa CL_ActorAdd */ void G_EventActorAdd (playermask_t playerMask, const Edict& ent) { G_EventAdd(playerMask, EV_ACTOR_ADD, ent.getIdNum()); gi.WriteByte(ent.getTeam()); gi.WriteByte(ent.chr.teamDef ? ent.chr.teamDef->idx : NONE); gi.WriteByte(ent.chr.gender); gi.WriteByte(ent.getPlayerNum()); gi.WriteGPos(ent.pos); gi.WriteShort(ent.state & STATE_PUBLIC); gi.WriteByte(ent.fieldSize); G_EventEnd(); }
void G_EventModelExplode (const Edict& ent, const char* sound) { assert(ent.inuse); G_EventAdd(PM_ALL, EV_MODEL_EXPLODE, ent.getIdNum()); gi.WriteString(sound); G_EventEnd(); }
/** * @brief Send disappear event * @param[in] playerMask The bitmask to determine the clients this event is send to * @param[in,out] ent The edict that perished */ void G_EventEdictPerish (playermask_t playerMask, const Edict& ent) { assert(ent.inuse); G_EventAdd(playerMask, EV_ENT_PERISH, ent.getIdNum()); gi.WriteByte(ent.type); G_EventEnd(); }
/** * @brief Send an appear event to the client. * @param playerMask The players to send the event to * @param ent The edict that should appear to the players included in the given mask. * @note Each following event that is relying on the fact that this edict must already * be known in the client, must also adopt the client side parsing of the event times. */ void G_EventEdictAppear (playermask_t playerMask, const Edict& ent) { G_EventAdd(playerMask, EV_ENT_APPEAR, ent.getIdNum()); gi.WriteByte(ent.type); gi.WriteGPos(ent.pos); G_EventEnd(); }
void G_EventReactionFireAddTarget (const Edict& shooter, const Edict& target, int tus, int step) { gi.QueueEvent(G_PlayerToPM(shooter.getPlayer()), EV_ACTOR_REACTIONFIREADDTARGET, shooter.number); gi.QueueWriteShort(target.number); gi.QueueWriteByte(tus); gi.QueueWriteByte(step); }
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 Called every frame to let the edicts tick */ void G_EdictsThink (void) { /* treat each object in turn */ /* even the world gets a chance to think */ Edict* ent = nullptr; while ((ent = G_EdictsGetNextInUse(ent))) { if (!ent->think) continue; if (ent->nextthink <= 0) continue; if (ent->nextthink > level.time + 0.001f) continue; ent->nextthink = level.time + SERVER_FRAME_SECONDS; ent->think(ent); } }
void G_EventSpawnFootstepSound (const Edict& ent, const char* sound) { const playermask_t playerMask = ~G_VisToPM(ent.visflags); gi.QueueEvent(playerMask, EV_SOUND, ent.getIdNum()); gi.QueueWritePos(ent.origin); gi.QueueWriteByte(ent.moveinfo.steps); gi.QueueWriteString(sound); }
/** * Send a particle spawn event to the client * @param[in] playerMask The clients that should see the particle * @param[in] ent The particle to spawn */ void G_EventSendParticle (playermask_t playerMask, const Edict& ent) { G_EventAdd(playerMask, EV_PARTICLE_APPEAR, ent.getIdNum()); gi.WriteShort(ent.spawnflags); gi.WritePos(ent.origin); gi.WriteString(ent.particle); G_EventEnd(); }
/** * @brief Start the shooting event * @param ent The entity that starts the shooting * @param teamMask the vis mask of the teams to determine the clients from this event is send to * @param shootType The type of the shoot * @param at The grid position to target to */ void G_EventStartShoot (const Edict& ent, teammask_t teamMask, shoot_types_t shootType, const pos3_t at) { G_EventAdd(G_VisToPM(teamMask), EV_ACTOR_START_SHOOT, ent.getIdNum()); gi.WriteByte(shootType); gi.WriteGPos(ent.pos); gi.WriteGPos(at); G_EventEnd(); }
/** * @brief Tell the client to remove the item from the container * @param[in] ent Pointer to entity having given inventory. * @param[in] playerMask The player mask to determine which clients should receive the event (e.g. @c G_VisToPM(ent->visflags)) * @param[in] containerId Id of the container the item is in. * @param[in] x,y Position of item in container. */ void G_EventInventoryDelete (const Edict& ent, playermask_t playerMask, const containerIndex_t containerId, int x, int y) { G_EventAdd(playerMask, EV_INV_DEL, ent.getIdNum()); gi.WriteByte(containerId); gi.WriteByte(x); gi.WriteByte(y); G_EventEnd(); }
/** * @brief Either finds a free edict, or allocates a new one. * @note Try to avoid reusing an entity that was recently freed, because it * can cause the player to think the entity morphed into something else * instead of being removed and recreated, which can cause interpolated * angles and bad trails. * @sa G_FreeEdict */ Edict* G_Spawn (const char* classname) { Edict* ent = G_EdictsGetNewEdict(); if (!ent) gi.Error("G_Spawn: no free edicts"); ent->inuse = true; ent->number = G_EdictsGetNumber(ent); if (classname) ent->classname = classname; else ent->classname = "noclass"; ent->fieldSize = ACTOR_SIZE_NORMAL; ent->setActive(); /* only used by camera */ return ent; }
/** * @brief Send the bounding box info to the client. * @param[in] ent The edict to send the bounding box for */ void G_EventSendEdict (const Edict& ent) { G_EventAdd(PM_ALL, EV_ADD_EDICT, ent.getIdNum()); gi.WriteByte(ent.type); gi.WritePos(ent.absBox.mins); gi.WritePos(ent.absBox.maxs); G_EventEnd(); }
/** * @brief Triggers the end of the game. Will be executed in the next server (or game) frame. * @param[in] team The winning team * @param[in] timeGap Second to wait before really ending the game. Useful if you want to allow a last view on the scene */ void G_MatchEndTrigger (int team, int timeGap) { bool foundNextMap = false; Edict* ent = nullptr; while ((ent = G_EdictsGetTriggerNextMaps(ent)) != nullptr) { if (ent->getTeam() == team) { ent->think = Think_NextMapTrigger; ent->nextthink = 1; foundNextMap = true; } } if (!foundNextMap) { const int realTimeGap = timeGap > 0 ? level.time + timeGap : 1; level.winningTeam = team; level.intermissionTime = realTimeGap; } }
void G_EventInventoryReload (const Edict& ent, playermask_t playerMask, const Item* item, const invDef_t* invDef, const Item* ic) { G_EventAdd(playerMask, EV_INV_RELOAD, ent.getIdNum()); gi.WriteByte(item->def()->ammo); gi.WriteByte(item->ammoDef()->idx); gi.WriteByte(invDef->id); gi.WriteByte(ic->getX()); gi.WriteByte(ic->getY()); G_EventEnd(); }
/** * @brief Mission trigger use function */ bool G_MissionUse (Edict* self, Edict* activator) { Edict* target = G_EdictsFindTargetEntity(self->target); if (!target) { gi.DPrintf("Target '%s' wasn't found for misc_mission\n", self->target); G_FreeEdict(self); return false; } if (target->destroy) { /* set this to zero to determine that this is a triggered destroy call */ target->HP = 0; target->destroy(target); /* freed when the level changes */ self->target = nullptr; self->use = nullptr; } else if (target->use) target->use(target, activator); return true; }
void G_EventActorFall (const Edict& ent) { G_EventAdd(G_VisToPM(ent.visflags), EV_ACTOR_MOVE, ent.getIdNum()); gi.WriteByte(1); gi.WriteByte(ent.pos[0]); gi.WriteByte(ent.pos[1]); gi.WriteByte(ent.pos[2]); gi.WriteByte(makeDV(DIRECTION_FALL, ent.pos[2])); gi.WriteShort(GRAVITY); gi.WriteShort(0); G_EventEnd(); }