/** * @sa G_MoraleStopPanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleBehaviour */ static void G_MoralePanic (Edict* ent) { G_ClientPrintf(ent->getPlayer(), PRINT_HUD, _("%s panics!"), ent->chr.name); G_PrintStats("%s panics (entnum %i).", ent->chr.name, ent->getIdNum()); /* drop items in hands */ if (G_IsInsane(ent) && ent->chr.teamDef->weapons) { if (ent->getRightHandItem()) G_ActorInvMove(ent, INVDEF(CID_RIGHT), ent->getRightHandItem(), INVDEF(CID_FLOOR), NONE, NONE, true); if (ent->getLeftHandItem()) G_ActorInvMove(ent, INVDEF(CID_LEFT), ent->getLeftHandItem(), INVDEF(CID_FLOOR), NONE, NONE, true); G_ClientStateChange(ent->getPlayer(), ent, ~STATE_REACTION, false); } /* get up */ G_RemoveCrouched(ent); G_ActorSetMaxs(ent); /* send panic */ G_SetPanic(ent); G_EventSendState(G_VisToPM(ent->visflags), *ent); /* center view */ G_EventCenterView(*ent); /* move around a bit, try to avoid opponents */ AI_ActorThink(ent->getPlayer(), ent); /* kill TUs */ G_ActorSetTU(ent, 0); }
/** * @sa G_MoraleStopPanic * @sa G_MoraleRage * @sa G_MoraleStopRage * @sa G_MoraleBehaviour */ static void G_MoralePanic (edict_t * ent, qboolean sanity) { G_ClientPrintf(G_PLAYER_FROM_ENT(ent), PRINT_HUD, _("%s panics!\n"), ent->chr.name); /* drop items in hands */ if (!sanity && ent->chr.teamDef->weapons) { if (RIGHT(ent)) G_ActorInvMove(ent, INVDEF(gi.csi->idRight), RIGHT(ent), INVDEF(gi.csi->idFloor), NONE, NONE, qtrue); if (LEFT(ent)) G_ActorInvMove(ent, INVDEF(gi.csi->idLeft), LEFT(ent), INVDEF(gi.csi->idFloor), NONE, NONE, qtrue); } /* get up */ G_RemoveCrouched(ent); G_ActorSetMaxs(ent); /* send panic */ G_SetPanic(ent); G_EventSendState(G_VisToPM(ent->visflags), ent); /* center view */ G_EventCenterView(ent); /* move around a bit, try to avoid opponents */ AI_ActorThink(G_PLAYER_FROM_ENT(ent), ent); /* kill TUs */ G_ActorSetTU(ent, 0); }
/** * @brief Sends whole inventory through the network buffer. * @param[in] playerMask The player mask to determine which clients should receive the event (@c G_VisToPM(ent->visflags)). * @param[in] ent Pointer to an actor or floor container with inventory to send. * @sa G_AppearPerishEvent * @sa CL_InvAdd */ void G_SendInventory (playermask_t playerMask, const edict_t *ent) { invList_t *ic; int nr = 0; containerIndex_t container; /* test for pointless player mask */ if (!playerMask) return; for (container = 0; container < gi.csi->numIDs; container++) { if (!G_IsItem(ent) && INVDEF(container)->temp) continue; for (ic = CONTAINER(ent, container); ic; ic = ic->next) nr++; } /* return if no inventory items to send */ if (nr == 0) return; G_EventInventoryAdd(ent, playerMask, nr); for (container = 0; container < gi.csi->numIDs; container++) { if (!G_IsItem(ent) && INVDEF(container)->temp) continue; for (ic = CONTAINER(ent, container); ic; ic = ic->next) { /* send a single item */ assert(ic->item.item); G_WriteItem(&ic->item, INVDEF(container), ic->x, ic->y); } } G_EventEnd(); }
/** * @brief Mission trigger * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger * @todo use level.actualRound to determine the 'King of the Hill' time * @note Don't set a client action here - otherwise the movement event might * be corrupted */ bool G_MissionTouch (edict_t *self, edict_t *activator) { if (!self->owner) return false; switch (self->owner->team) { case TEAM_ALIEN: if (G_IsAlien(activator)) { if (!self->count) { self->count = level.actualRound; gi.BroadcastPrintf(PRINT_HUD, _("Aliens entered target zone!")); } return true; } else { /* reset king of the hill counter */ self->count = 0; } /* general case that also works for multiplayer teams */ default: if (activator->team == self->owner->team) { if (!self->owner->count) { self->owner->count = level.actualRound; if (self->owner->item) { /* search the item in the activator's inventory */ containerIndex_t container; for (container = 0; container < gi.csi->numIDs; container++) { const invDef_t *invDef = INVDEF(container); invList_t *ic; /* ignore items linked from any temp container the actor * must have this in his hands */ if (invDef->temp) continue; for (ic = CONTAINER(activator, container); ic; ic = ic->next) { const objDef_t *od = ic->item.t; /* check whether we found the searched item in the * actor's inventory */ if (Q_streq(od->id, self->owner->item)) { /* drop the weapon - even if out of TUs */ G_ActorInvMove(activator, invDef, ic, INVDEF(gi.csi->idFloor), NONE, NONE, false); gi.BroadcastPrintf(PRINT_HUD, _("Item was placed.")); self->owner->count = level.actualRound; return true; } } } } else { gi.BroadcastPrintf(PRINT_HUD, _("Target zone is occupied!")); } } return true; } else { /* reset king of the hill counter */ self->count = 0; } } return false; }
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); } }
/** * @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; } }
static bool UI_ContainerNodeAddItem (const invDef_t *container, invList_t *ic, containerIndex_t containerID, invList_t **icp) { int px, py; const invDef_t *target = INVDEF(containerID); INVSH_FindSpace(ui_inventory, &ic->item, target, &px, &py, NULL); return INV_MoveItem(ui_inventory, target, px, py, container, ic, icp); }
/** * @brief Checks whether the given container contains items that should be * dropped to the floor. Also removes virtual items. * @param[in,out] ent The entity to check the inventory containers for. The inventory of * this edict is modified in this function (the virtual items are removed). * @param[in] container The container of the entity inventory to check * @return @c true if there are items that should be dropped to floor, @c false otherwise */ static bool G_InventoryDropToFloorCheck (edict_t* ent, containerIndex_t container) { invList_t* ic = CONTAINER(ent, container); if (container == gi.csi->idArmour) return false; if (ic) { bool check = false; while (ic) { assert(ic->item.item); if (ic->item.item->isVirtual) { invList_t *next = ic->next; /* remove the virtual item to update the inventory lists */ if (!game.i.RemoveFromInventory(&game.i, &ent->chr.i, INVDEF(container), ic)) gi.Error("Could not remove virtual item '%s' from inventory %i", ic->item.item->id, container); ic = next; } else { /* there are none virtual items left that should be send to the client */ check = true; ic = ic->next; } } return check; } return false; }
/** * @brief Fills the ground container of the ui_inventory with unused items from a given * equipment definition * @note Keep in mind that @c ed is changed here - so items are removed and the ground container * of a inventory definition is in general a temp container - that means you should make a copy * of the @c equipDef_t you want to add to the temp ground container of the given @c inv * @todo it's not obvious for the caller that @c ui_inventory pointer must be set * @param[in,out] inv The inventory to add the unused items from @c ed to * @param[in,out] ed The equipment definition to get the used items from that should be added * to the ground container of @c inv * @todo not used nor called by the container node; should be move somewhere else */ void UI_ContainerNodeUpdateEquipment (inventory_t *inv, const equipDef_t *ed) { int i; int *const numItems = Mem_Dup(int, ed->numItems, lengthof(ed->numItems)); /* a 'tiny hack' to add the remaining equipment (not carried) * correctly into buy categories, reloading at the same time; * it is valid only due to the following property: */ assert(MAX_CONTAINERS >= FILTER_AIRCRAFT); for (i = 0; i < csi.numODs; i++) { const objDef_t *od = INVSH_GetItemByIDX(i); /* Don't allow to show unuseable items. */ if (!GAME_ItemIsUseable(od)) continue; while (numItems[i]) { const item_t item = {NONE_AMMO, NULL, od, 0, 0}; if (!cls.i.AddToInventory(&cls.i, inv, &item, INVDEF(csi.idEquip), NONE, NONE, 1)) { /* no space left in the inventory */ break; } numItems[item.item->idx]--; } } Mem_Free(numItems); /* First-time linking of ui_inventory. */ if (ui_inventory && !ui_inventory->c[csi.idEquip]) { ui_inventory->c[csi.idEquip] = inv->c[csi.idEquip]; } }
void CL_InvAmmo (const eventRegister_t *self, dbuffer *msg) { invList_t *ic; le_t *le; int number; int ammo, type, x, y; containerIndex_t container; NET_ReadFormat(msg, self->formatString, &number, &ammo, &type, &container, &x, &y); le = LE_Get(number); if (!le) { Com_DPrintf(DEBUG_CLIENT, "InvAmmo message ignored... LE not found\n"); return; } if (le->team != cls.team) return; assert(container >= 0); assert(container < MAX_INVDEFS); ic = INVSH_SearchInInventory(&le->i, INVDEF(container), x, y); if (!ic) return; /* set new ammo */ ic->item.ammoLeft = ammo; ic->item.ammo = INVSH_GetItemByIDX(type); }
void CL_InvAmmo (const eventRegister_t* self, dbuffer* msg) { int number; int ammo, type, x, y; containerIndex_t container; NET_ReadFormat(msg, self->formatString, &number, &ammo, &type, &container, &x, &y); le_t* le = LE_Get(number); if (!le) { Com_DPrintf(DEBUG_CLIENT, "InvAmmo message ignored... LE not found\n"); return; } if (le->team != cls.team) return; assert(container >= 0); assert(container < MAX_INVDEFS); Item* item = le->inv.getItemAtPos(INVDEF(container), x, y); if (!item) return; /* set new ammo */ item->setAmmoLeft(ammo); item->setAmmoDef(INVSH_GetItemByIDX(type)); }
/** * @brief Reload weapon with actor. * @param[in] ent Pointer to an actor reloading weapon. * @param[in] invDef Reloading weapon in right or left hand. * @sa AI_ActorThink */ void G_ActorReload (Edict* ent, const invDef_t* invDef) { const objDef_t* weapon; if (ent->getContainer(invDef->id)) { weapon = ent->getContainer(invDef->id)->def(); } else if (invDef->isLeftDef() && ent->getRightHandItem()->isHeldTwoHanded()) { /* Check for two-handed weapon */ invDef = INVDEF(CID_RIGHT); weapon = ent->getRightHandItem()->def(); } else return; assert(weapon); /* LordHavoc: Check if item is researched when in singleplayer? I don't think this is really a * cheat issue as in singleplayer there is no way to inject fake client commands in the virtual * network buffer, and in multiplayer everything is researched */ /* search for clips and select the one that is available easily */ /* also try the temp containers */ const invDef_t* bestContainer = nullptr; Item* ammoItem = nullptr; int tu = 100; const Container* cont = nullptr; while ((cont = ent->chr.inv.getNextCont(cont, true))) { if (cont->def()->out >= tu) continue; /* Once we've found at least one clip, there's no point * searching other containers if it would take longer * to retrieve the ammo from them than the one * we've already found. */ Item* item = nullptr; while ((item = cont->getNextItem(item))) { if (item->def()->isLoadableInWeapon(weapon)) { ammoItem = item; bestContainer = INVDEF(cont->id); tu = bestContainer->out; break; } } } /* send request */ if (bestContainer) G_ActorInvMove(ent, bestContainer, ammoItem, invDef, 0, 0, true); }
/** * @brief Removes one particular item from a given container * @param itemID The id of the item to remove * @param ent The edict that holds the inventory to remove the item from * @param container The container in the inventory of the edict to remove the searched item from. * @return @c true if the removal was successful, @c false otherwise. */ bool G_InventoryRemoveItemByID (const char *itemID, edict_t *ent, containerIndex_t container) { invList_t *ic = CONTAINER(ent, container); while (ic) { const objDef_t *item = ic->item.item; if (item != NULL && Q_streq(item->id, itemID)) { /* remove the virtual item to update the inventory lists */ if (!game.i.RemoveFromInventory(&game.i, &ent->chr.i, INVDEF(container), ic)) gi.Error("Could not remove item '%s' from inventory %i", ic->item.item->id, container); G_EventInventoryDelete(ent, G_VisToPM(ent->visflags), INVDEF(container), ic->x, ic->y); return true; } ic = ic->next; } return false; }
/** * @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 Mission trigger * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger * @todo use level.actualRound to determine the 'King of the Hill' time * @note Don't set a client action here - otherwise the movement event might * be corrupted */ bool G_MissionTouch (Edict* self, Edict* activator) { if (!self->owner()) return false; switch (self->owner()->getTeam()) { case TEAM_ALIEN: if (G_IsAlien(activator)) { if (!self->count) { self->count = level.actualRound; gi.BroadcastPrintf(PRINT_HUD, _("Aliens entered target zone!")); } return true; } else { /* reset king of the hill counter */ self->count = 0; } /* general case that also works for multiplayer teams */ default: if (!activator->isSameTeamAs(self->owner())) { /* reset king of the hill counter */ self->count = 0; return false; } if (self->owner()->count) return false; self->owner()->count = level.actualRound; if (!self->owner()->item) { gi.BroadcastPrintf(PRINT_HUD, _("Target zone is occupied!")); return true; } /* search the item in the activator's inventory */ /* ignore items linked from any temp container the actor must have this in his hands */ const Container* cont = nullptr; while ((cont = activator->chr.inv.getNextCont(cont))) { Item* item = nullptr; while ((item = cont->getNextItem(item))) { const objDef_t* od = item->def(); /* check whether we found the searched item in the actor's inventory */ if (!Q_streq(od->id, self->owner()->item)) continue; /* drop the weapon - even if out of TUs */ G_ActorInvMove(makeActor(activator), cont->def(), item, INVDEF(CID_FLOOR), NONE, NONE, false); gi.BroadcastPrintf(PRINT_HUD, _("Item was placed.")); self->owner()->count = level.actualRound; return true; } } break; } return true; }
/** * @brief Retrieve or collect weapon from any linked container for the actor * @note This function will also collect items from floor containers when the actor * is standing on a given point. * @sa AI_ActorThink */ void G_ClientGetWeaponFromInventory (edict_t *ent) { invList_t *ic; invList_t *icFinal; const invDef_t *invDef; int tu; containerIndex_t container; const invDef_t *bestContainer; /* e.g. bloodspiders are not allowed to carry or collect weapons */ if (!ent->chr.teamDef->weapons) return; /* search for weapons and select the one that is available easily */ tu = 100; invDef = INVDEF(gi.csi->idRight); bestContainer = NULL; icFinal = NULL; /* also try the temp containers */ for (container = 0; container < gi.csi->numIDs; container++) { if (INVDEF(container)->out < tu) { /* Once we've found at least one clip, there's no point * searching other containers if it would take longer * to retrieve the ammo from them than the one * we've already found. */ for (ic = CONTAINER(ent, container); ic; ic = ic->next) { assert(ic->item.item); if (ic->item.item->weapon && (ic->item.ammoLeft > 0 || !ic->item.item->reload)) { icFinal = ic; bestContainer = INVDEF(container); tu = bestContainer->out; break; } } } } /* send request */ if (bestContainer) G_ActorInvMove(ent, bestContainer, icFinal, invDef, 0, 0, true); }
/** * @brief Called whenever an entity disappears from view * @sa CL_EntAppear */ void CL_EntPerish (const eventRegister_t *self, struct dbuffer *msg) { int entnum; int type; le_t *le, *actor; NET_ReadFormat(msg, self->formatString, &entnum, &type); le = LE_Get(entnum); if (!le) LE_NotFoundWithTypeError(entnum, type); 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 a 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; /* decrease the count of spotted aliens (also stunned) */ cl.numEnemiesSpotted = CL_CountVisibleEnemies(); }
static bool G_InventoryPlaceItemAdjacent (edict_t *ent) { vec2_t oldPos; /* if we have to place it to adjacent */ edict_t *floorAdjacent; int i; Vector2Copy(ent->pos, oldPos); floorAdjacent = NULL; for (i = 0; i < DIRECTIONS; i++) { /** @todo Check whether movement is possible here - otherwise don't use this field */ /* extend pos with the direction vectors */ /** @todo Don't know why the adjacent stuff has been disabled, but if it was buggy, it's probably */ /** because the third ent->pos in the next line should be pos[1] ?!. (Duke, 13.1.11) */ Vector2Set(ent->pos, ent->pos[0] + dvecs[i][0], ent->pos[0] + dvecs[i][1]); /* now try to get a floor entity for that new location */ floorAdjacent = G_GetFloorItems(ent); if (!floorAdjacent) { floorAdjacent = G_SpawnFloor(ent->pos); } else { /* destroy this edict (send this event to all clients that see the edict) */ G_EventPerish(floorAdjacent); G_VisFlagsReset(floorAdjacent); } INVSH_FindSpace(&floorAdjacent->i, &ic->item, INVDEF(gi.csi->idFloor), &x, &y, ic); if (x != NONE) { ic->x = x; ic->y = y; ic->next = FLOOR(floorAdjacent); FLOOR(floorAdjacent) = ic; break; } /* restore original pos */ Vector2Copy(oldPos, ent->pos); } /* added to adjacent pos? */ if (i < DIRECTIONS) { /* restore original pos - if no free space, this was done * already in the for loop */ Vector2Copy(oldPos, ent->pos); return false; } if (floorAdjacent) G_CheckVis(floorAdjacent, true); return true; }
/** * @brief Draws the rectangle in a 'free' style on position posx/posy (pixel) in the size sizex/sizey (pixel) */ static void UI_DrawFree (containerIndex_t container, const uiNode_t *node, int posx, int posy, int sizex, int sizey, bool showTUs) { const vec4_t color = { 0.0f, 1.0f, 0.0f, 0.7f }; invDef_t* inv = INVDEF(container); vec2_t nodepos; UI_GetNodeAbsPos(node, nodepos); UI_DrawFill(posx, posy, sizex, sizey, color); /* if showTUs is true (only the first time in none single containers) * and we are connected to a game */ if (showTUs && CL_BattlescapeRunning()) { UI_DrawString("f_verysmall", ALIGN_UL, nodepos[0] + 3, nodepos[1] + 3, nodepos[0] + 3, node->box.size[0] - 6, 0, va(_("In: %i Out: %i"), inv->in, inv->out)); } }
/** * @brief Adds a new item to an existing or new floor container edict at the given grid location * @param pos The grid location to spawn the item on the floor * @param itemID The item to spawn */ bool G_AddItemToFloor (const pos3_t pos, const char *itemID) { edict_t *floor; item_t item = {NONE_AMMO, NULL, NULL, 0, 0}; const objDef_t *od = INVSH_GetItemByIDSilent(itemID); if (!od) { gi.DPrintf("Could not find item '%s'\n", itemID); return false; } /* Also sets FLOOR(ent) to correct value. */ floor = G_GetFloorItemsFromPos(pos); /* nothing on the ground yet? */ if (!floor) floor = G_SpawnFloor(pos); item.item = od; return game.i.TryAddToInventory(&game.i, &floor->chr.i, &item, INVDEF(gi.csi->idFloor)); }
void CL_InvReload (const eventRegister_t* self, dbuffer* msg) { int number; int ammo, type, x, y; containerIndex_t container; NET_ReadFormat(msg, self->formatString, &number, &ammo, &type, &container, &x, &y); le_t* le = LE_Get(number); if (!le) return; if (le->team != cls.team) return; assert(container >= 0); assert(container < MAX_INVDEFS); Item* ic = le->inv.getItemAtPos(INVDEF(container), x, y); if (!ic) return; S_LoadAndPlaySample(ic->def()->reloadSound, le->origin, ic->def()->reloadAttenuation, SND_VOLUME_WEAPONS); /* if the displaced clip had any remaining bullets * store them as loose, unless the removed clip was full */ equipDef_t* ed = GAME_GetEquipmentDefinition(); if (ed && ic->getAmmoLeft() > 0 && ic->getAmmoLeft() != ic->def()->ammo) { assert(ammo == ic->def()->ammo); /* Accumulate loose ammo into clips (only accessible post-mission) */ ed->addClip(ic); } /* set new ammo */ ic->setAmmoLeft(ammo); ic->setAmmoDef(INVSH_GetItemByIDX(type)); if (le == selActor) Cmd_ExecuteString("hud_updateactorload"); }
/** * @brief Read item from the network buffer * @param[in,out] item @c item_t being send through net. * @param[in,out] container Container which is being updated with item sent. * @param[in] x Position of item in given container. * @param[in] y Position of item in given container. * @sa CL_NetReceiveItem * @sa EV_INV_TRANSFER */ void G_ReadItem (item_t *item, const invDef_t **container, int *x, int *y) { int t, m; containerIndex_t containerID; gi.ReadFormat("sbsbbbbs", &t, &item->ammoLeft, &m, &containerID, x, y, &item->rotated, &item->amount); if (t < 0 || t >= gi.csi->numODs) gi.Error("Item index out of bounds: %i", t); item->item = &gi.csi->ods[t]; if (m != NONE) { if (m < 0 || m >= gi.csi->numODs) gi.Error("Ammo index out of bounds: %i", m); item->ammo = &gi.csi->ods[m]; } else { item->ammo = NULL; } if (containerID >= 0 && containerID < gi.csi->numIDs) *container = INVDEF(containerID); else gi.Error("container id is out of bounds: %i", containerID); }
/** * @brief Actor reloads his weapons. */ static int AIL_reload (lua_State *L) { containerIndex_t container; if (lua_gettop(L) > 0) { if (lua_isstring(L, 1)) { const char *s = lua_tostring(L, 1); if (Q_streq(s, "right")) container = gi.csi->idRight; else if (Q_streq(s, "left")) container = gi.csi->idLeft; else return 0; } else { AIL_invalidparameter(1); return 0; } } else container = gi.csi->idRight; /* Default to right hand. */ G_ActorReload(AIL_ent, INVDEF(container)); return 0; }
/** * @brief Try to autoplace an item from a container. * @param[in] node The context node * @param[in] ic An item from the node container * @todo We should use an item ID, and get the item inside the function, to avoid wrong uses. */ void UI_ContainerNodeAutoPlaceItem (uiNode_t* node, invList_t *ic) { containerIndex_t target; uiNode_t *targetNode; bool ammoChanged = false; const invDef_t *container = EXTRADATA(node).container; /* Right click: automatic item assignment/removal. */ if (container->id != csi.idEquip) { if (ic->item.ammo && ic->item.ammo != ic->item.item && ic->item.ammoLeft) { /* Remove ammo on removing weapon from a soldier */ target = csi.idEquip; ammoChanged = INV_UnloadWeapon(ic, ui_inventory, INVDEF(target)); } else { /* Move back to idEquip (ground, floor) container. */ target = csi.idEquip; INV_MoveItem(ui_inventory, INVDEF(target), NONE, NONE, container, ic, NULL); } } else { bool packed = false; assert(ic->item.item); /* armour can only have one target */ if (INV_IsArmour(ic->item.item)) { target = csi.idArmour; packed = INV_MoveItem(ui_inventory, INVDEF(target), 0, 0, container, ic, NULL); /* ammo or item */ } else if (INV_IsAmmo(ic->item.item)) { /* Finally try left and right hand. There is no other place to put it now. */ const containerIndex_t idxArray[] = { csi.idBelt, csi.idHolster, csi.idBackpack, csi.idLeft, csi.idRight }; const size_t size = lengthof(idxArray); unsigned int i; for (i = 0; i < size; i++) { target = idxArray[i]; packed = UI_ContainerNodeAddItem(container, ic, target, NULL); if (packed) break; } } else { if (ic->item.item->headgear) { target = csi.idHeadgear; packed = UI_ContainerNodeAddItem(container, ic, target, NULL); } else { /* left and right are single containers, but this might change - it's cleaner to check * for available space here, too */ const containerIndex_t idxArray[] = { csi.idRight, csi.idBelt, csi.idHolster, csi.idBackpack, csi.idLeft }; const size_t size = lengthof(idxArray); invList_t *tItem = NULL; unsigned int i; for (i = 0; i < size; i++) { target = idxArray[i]; packed = UI_ContainerNodeAddItem(container, ic, target, &tItem); if (packed) { if ((ic->item.item->weapon && !ic->item.ammoLeft) || ic->item.item->oneshot) ammoChanged = INV_LoadWeapon(tItem, ui_inventory, container, INVDEF(target)); break; } } } } /* no need to continue here - placement wasn't successful at all */ if (!packed) return; } /* Run onChange events */ targetNode = UI_GetContainerNodeByContainerIDX(node->parent, target); if (node->onChange) UI_ExecuteEventActions(node, node->onChange); if (targetNode != NULL && node != targetNode && targetNode->onChange) UI_ExecuteEventActions(targetNode, targetNode->onChange); /* Also call onChange for equip_ammo if ammo moved * Maybe there's a better way to do this? */ if (INV_IsAmmo(ic->item.item) || ammoChanged) { /** @todo hard coded node name, remove it when it is possible */ uiNode_t *ammoNode = UI_GetNode(node->root, "equip_ammo"); if (ammoNode != NULL && node != ammoNode && ammoNode->onChange) UI_ExecuteEventActions(ammoNode, ammoNode->onChange); } }
/** * @brief The client sent us a message that he did something. We now execute the related function(s) and notify him if necessary. * @param[in] player The player to execute the action for (the actor belongs to this player) * @note a client action will also send the server side edict number to determine the actor */ int G_ClientAction (player_t * player) { player_action_t action; int num; pos3_t pos; int i; fireDefIndex_t firemode; int from, fx, fy, to, tx, ty; actorHands_t hand; int fmIdx, objIdx; int resCrouch, resShot; edict_t *ent; const char *format; /* read the header */ action = (player_action_t)gi.ReadByte(); num = gi.ReadShort(); ent = G_EdictsGetByNum(num); if (ent == NULL) return action; format = pa_format[action]; switch (action) { case PA_NULL: /* do nothing on a null action */ break; case PA_TURN: gi.ReadFormat(format, &i); G_ClientTurn(player, ent, (dvec_t) i); break; case PA_MOVE: gi.ReadFormat(format, &pos); G_ClientMove(player, player->pers.team, ent, pos); break; case PA_STATE: gi.ReadFormat(format, &i); G_ClientStateChange(player, ent, i, true); break; case PA_SHOOT: gi.ReadFormat(format, &pos, &i, &firemode, &from); G_ClientShoot(player, ent, pos, i, firemode, NULL, true, from); break; case PA_INVMOVE: gi.ReadFormat(format, &from, &fx, &fy, &to, &tx, &ty); if (from < 0 || from >= gi.csi->numIDs || to < 0 || to >= gi.csi->numIDs) { gi.DPrintf("G_ClientAction: PA_INVMOVE Container index out of range. (from: %i, to: %i)\n", from, to); } else { const invDef_t *fromPtr = INVDEF(from); const invDef_t *toPtr = INVDEF(to); invList_t *fromItem = INVSH_SearchInInventory(&ent->chr.i, fromPtr, fx, fy); if (fromItem) G_ActorInvMove(ent, fromPtr, fromItem, toPtr, tx, ty, true); } break; case PA_USE: if (ent->clientAction) { edict_t *actionEnt; /* read the door the client wants to open */ gi.ReadFormat(format, &i); /* get the door edict */ actionEnt = G_EdictsGetByNum(i); /* maybe the door is no longer 'alive' because it was destroyed */ if (actionEnt && ent->clientAction == actionEnt) { if (G_IsDoor(actionEnt)) { G_ActorUseDoor(ent, actionEnt); } } } break; case PA_REACT_SELECT: gi.ReadFormat(format, &hand, &fmIdx, &objIdx); G_ReactionFireSettingsUpdate(ent, fmIdx, hand, INVSH_GetItemByIDX(objIdx)); break; case PA_RESERVE_STATE: gi.ReadFormat(format, &resShot, &resCrouch); G_ActorReserveTUs(ent, ent->chr.reservedTus.reaction, resShot, resCrouch); break; default: gi.Error("G_ClientAction: Unknown action!\n"); } return action; }
/** * @brief Mission trigger * @todo use level.nextmap to spawn another map when every living actor has touched the mission trigger * @note Don't set a client action here - otherwise the movement event might * be corrupted */ bool G_MissionTouch (Edict* self, Edict* activator) { if (!G_IsLivingActor(activator)) return false; Actor* actor = makeActor(activator); const char* const actorTeam = G_MissionGetTeamString(actor->getTeam()); if (!G_IsCivilian(actor) && self->isOpponent(actor)) { if (!self->item && self->count) { if (self->targetname) { gi.BroadcastPrintf(PRINT_HUD, _("%s forces are attacking the %s!"), actorTeam, self->targetname); } else { const char* const teamName = G_MissionGetTeamString(self->getTeam()); gi.BroadcastPrintf(PRINT_HUD, _("%s forces are attacking %s target zone!"), actorTeam, teamName); } /* reset king of the hill counter */ self->count = 0; } return false; } if (self->count) return false; if (self->isSameTeamAs(actor)) { self->count = level.actualRound; if (!self->item) { linkedList_t* touched = self->touchedList; while (touched) { const Edict* const ent = static_cast<const Edict* const>(touched->data); if (!self->isSameTeamAs(ent) && !G_IsDead(ent)) { return true; } touched = touched->next; } if (self->targetname) { gi.BroadcastPrintf(PRINT_HUD, _("%s forces have occupied the %s!"), actorTeam, self->targetname); } else { gi.BroadcastPrintf(PRINT_HUD, _("%s forces have occupied their target zone!"), actorTeam); } return true; } } /* search the item in the activator's inventory */ /* ignore items linked from any temp container the actor must have this in his hands */ const Container* cont = nullptr; while ((cont = actor->chr.inv.getNextCont(cont))) { Item* item = nullptr; while ((item = cont->getNextItem(item))) { const objDef_t* od = item->def(); /* check whether we found the searched item in the actor's inventory */ if (!Q_streq(od->id, self->item)) continue; /* drop the weapon - even if out of TUs */ G_ActorInvMove(actor, cont->def(), item, INVDEF(CID_FLOOR), NONE, NONE, false); if (self->targetname) { gi.BroadcastPrintf(PRINT_HUD, _("The %s was placed at the %s."), item->def()->name, self->targetname); } else { gi.BroadcastPrintf(PRINT_HUD, _("The %s was placed."), item->def()->name); } self->count = level.actualRound; return true; } } return false; }
/** * @brief Move the whole given inventory to the floor and destroy the items that do not fit there. * @param[in] ent Pointer to an edict_t being an actor. * @sa G_ActorDie */ void G_InventoryToFloor (edict_t *ent) { invList_t *ic, *next; containerIndex_t container; edict_t *floor; item_t item; /* check for items */ for (container = 0; container < gi.csi->numIDs; container++) { /* ignore items linked from any temp container */ if (INVDEF(container)->temp) continue; if (G_InventoryDropToFloorCheck(ent, container)) break; } /* edict is not carrying any items */ if (container >= gi.csi->numIDs) return; /* find the floor */ floor = G_GetFloorItems(ent); if (!floor) { floor = G_SpawnFloor(ent->pos); } else { /* destroy this edict (send this event to all clients that see the edict) */ G_EventPerish(floor); G_VisFlagsReset(floor); } /* drop items */ /* cycle through all containers */ for (container = 0; container < gi.csi->numIDs; container++) { /* skip floor - we want to drop to floor */ if (container == gi.csi->idFloor) continue; /* skip csi->idArmour, we will collect armours using idArmour container, * not idFloor */ if (container == gi.csi->idArmour) continue; /* now cycle through all items for the container of the character (or the entity) */ for (ic = CONTAINER(ent, container); ic; ic = next) { /* Save the next inv-list before it gets overwritten below. * Do not put this in the "for" statement, * unless you want an endless loop. ;) */ next = ic->next; item = ic->item; /* only floor can summarize, so everything on the actor must have amount=1 */ assert(item.amount == 1); if (!game.i.RemoveFromInventory(&game.i, &ent->chr.i, INVDEF(container), ic)) gi.Error("Could not remove item '%s' from inventory %i of entity %i", ic->item.item->id, container, ent->number); if (game.i.AddToInventory(&game.i, &floor->chr.i, &item, INVDEF(gi.csi->idFloor), NONE, NONE, 1) == NULL) gi.Error("Could not add item '%s' from inventory %i of entity %i to floor container", ic->item.item->id, container, ent->number); #ifdef ADJACENT G_InventoryPlaceItemAdjacent(ent); #endif } /* destroy link */ CONTAINER(ent, container) = NULL; } FLOOR(ent) = FLOOR(floor); /* send item info to the clients */ G_CheckVis(floor); }
static void testInventoryWithTwoDiedAliensOnTheSameGridTile (void) { const char *mapName = "test_game"; if (FS_CheckFile("maps/%s.bsp", mapName) != -1) { edict_t *diedEnt; edict_t *diedEnt2; edict_t *ent; edict_t *floorItems; invList_t *invlist; int count; /* the other tests didn't call the server shutdown function to clean up */ OBJZERO(*sv); SV_Map(true, mapName, NULL); level.activeTeam = TEAM_ALIEN; /* first alien that should die and drop its inventory */ diedEnt = G_EdictsGetNextLivingActorOfTeam(NULL, TEAM_ALIEN); CU_ASSERT_PTR_NOT_NULL_FATAL(diedEnt); diedEnt->HP = 0; G_ActorDieOrStun(diedEnt, NULL); CU_ASSERT_TRUE_FATAL(G_IsDead(diedEnt)); /* second alien that should die and drop its inventory */ diedEnt2 = G_EdictsGetNextLivingActorOfTeam(NULL, TEAM_ALIEN); CU_ASSERT_PTR_NOT_NULL_FATAL(diedEnt2); /* move to the location of the first died alien to drop the inventory into the same floor container */ Player &player = diedEnt2->getPlayer(); CU_ASSERT_TRUE_FATAL(G_IsAIPlayer(&player)); G_ClientMove(player, 0, diedEnt2, diedEnt->pos); CU_ASSERT_TRUE_FATAL(VectorCompare(diedEnt2->pos, diedEnt->pos)); diedEnt2->HP = 0; G_ActorDieOrStun(diedEnt2, NULL); CU_ASSERT_TRUE_FATAL(G_IsDead(diedEnt2)); /* now try to collect the inventory with a third alien */ ent = G_EdictsGetNextLivingActorOfTeam(NULL, TEAM_ALIEN); CU_ASSERT_PTR_NOT_NULL_FATAL(ent); player = ent->getPlayer(); CU_ASSERT_TRUE_FATAL(G_IsAIPlayer(&player)); G_ClientMove(player, 0, ent, diedEnt->pos); CU_ASSERT_TRUE_FATAL(VectorCompare(ent->pos, diedEnt->pos)); floorItems = G_GetFloorItems(ent); CU_ASSERT_PTR_NOT_NULL_FATAL(floorItems); CU_ASSERT_PTR_EQUAL(floorItems->getFloor(), ent->getFloor()); /* drop everything to floor to make sure we have space in the backpack */ G_InventoryToFloor(ent); CU_ASSERT_EQUAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 0); invlist = ent->getContainer(CID_BACKPACK); CU_ASSERT_PTR_NULL_FATAL(invlist); count = GAMETEST_GetItemCount(ent, CID_FLOOR); if (count > 0) { invList_t *entryToMove = ent->getFloor(); int tx, ty; ent->chr.inv.findSpace(INVDEF(CID_BACKPACK), entryToMove, &tx, &ty, entryToMove); if (tx != NONE) { Com_Printf("trying to move item %s from floor into backpack to pos %i:%i\n", entryToMove->def()->name, tx, ty); CU_ASSERT_TRUE(G_ActorInvMove(ent, INVDEF(CID_FLOOR), entryToMove, INVDEF(CID_BACKPACK), tx, ty, false)); UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_FLOOR), count - 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name)); Com_Printf("item %s was removed from floor\n", entryToMove->def()->name); UFO_CU_ASSERT_EQUAL_INT_MSG_FATAL(GAMETEST_GetItemCount(ent, CID_BACKPACK), 1, va("item %s could not get moved successfully from floor into backpack", entryToMove->def()->name)); Com_Printf("item %s was moved successfully into the backpack\n", entryToMove->def()->name); invlist = ent->getContainer(CID_BACKPACK); CU_ASSERT_PTR_NOT_NULL_FATAL(invlist); } } SV_ShutdownGameProgs(); } else { UFO_CU_FAIL_MSG(va("Map resource '%s.bsp' for test is missing.", mapName)); } }
/** * @brief Search a child container node by the given container id * @note Only search with one depth */ uiNode_t *UI_GetContainerNodeByContainerIDX (const uiNode_t* const parent, const int index) { const invDef_t* const container = INVDEF(index); uiNode_t *containerNode = UI_GetNode(parent, container->name); return containerNode; }