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); } }
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; }
static void G_ActorRevitalise (Edict* ent) { if (G_IsStunned(ent)) { G_RemoveStunned(ent); /** @todo have a look at the morale value of * the ent and maybe get into rage or panic? */ G_ActorModifyCounters(ent->link, ent, 1, 0, -1); G_GetFloorItems(ent); } G_ActorSetMaxs(ent); /* check if the player appears/perishes, seen from other teams */ G_CheckVis(ent); /* calc new vis for this player */ G_CheckVisTeamAll(ent->team, 0, ent); G_PrintStats("%s is revitalized.", ent->chr.name); }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (Edict* self) { if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = nullptr; } Edict* chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const Item* ic; G_GetFloorItems(chain); ic = chain->getFloor(); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->getNext()) { const objDef_t* od = ic->def(); assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { /* Check that the target zone is still occupied (last defender might have died) */ if (!chain->item && !G_MissionIsTouched(chain)) { chain->count = 0; } const int endTime = level.actualRound - chain->count; const int spawnIndex = (chain->getTeam() + level.teamOfs) % MAX_TEAMS; const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS; /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || endTime < chain->time || (endTime == chain->time && spawnIndex < currentIndex)) return; } if (chain->target && !chain->time && !chain->item) { if (!G_MissionIsTouched(chain)) return; } } chain = chain->groupChain; } const bool endMission = self->target == nullptr; /* store team before the edict is released */ const int team = self->getTeam(); chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { G_UseEdict(chain, nullptr); if (chain->item != nullptr) { Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != nullptr) { if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum()); } else if (!item->getFloor()) { G_EventPerish(*item); G_FreeEdict(item); } } } if (chain->link != nullptr) { Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != nullptr) { G_AppearPerishEvent(G_VisToPM(particle->visflags), false, *particle, nullptr); G_FreeEdict(particle); } chain->link = nullptr; } /* Display mission message */ if (G_ValidMessage(chain)) { const char* msg = chain->message; if (msg[0] == '_') ++msg; gi.BroadcastPrintf(PRINT_HUD, "%s", msg); } } Edict* ent = chain->groupChain; /* free the group chain */ G_FreeEdict(chain); chain = ent; } if (endMission) G_MatchEndTrigger(team, level.activeTeam == TEAM_ALIEN ? 10 : 3); }
/** * @brief Generates the client events that are send over the netchannel to move an actor * @param[in] player Player who is moving an actor * @param[in] visTeam The team to check the visibility for - if this is 0 we build the forbidden list * above all edicts - for the human controlled actors this would mean that clicking to a grid * position that is not reachable because an invisible actor is standing there would not result in * a single step - as the movement is aborted before. For AI movement this is in general @c 0 - but * not if they e.g. hide. * @param[in] ent Edict to move * @param[in] to The grid position to walk to * @sa CL_ActorStartMove * @sa PA_MOVE */ void G_ClientMove (const player_t * player, int visTeam, edict_t* ent, const pos3_t to) { int status, initTU; dvec_t dvtab[MAX_DVTAB]; int dir; byte numdv, length; pos3_t pos; float div; int oldState; int oldHP; bool autoCrouchRequired = false; byte crouchingState; if (VectorCompare(ent->pos, to)) return; /* check if action is possible */ if (!G_ActionCheckForCurrentTeam(player, ent, TU_MOVE_STRAIGHT)) return; crouchingState = G_IsCrouched(ent) ? 1 : 0; oldState = oldHP = 0; /* calculate move table */ G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); /* length of ROUTING_NOT_REACHABLE means not reachable */ if (length && length >= ROUTING_NOT_REACHABLE) return; /* Autostand: check if the actor is crouched and player wants autostanding...*/ if (crouchingState && player->autostand) { /* ...and if this is a long walk... */ if (SHOULD_USE_AUTOSTAND(length)) { /* ...make them stand first. If the player really wants them to walk a long * way crouched, he can move the actor in several stages. * Uses the threshold at which standing, moving and crouching again takes * fewer TU than just crawling while crouched. */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); /* change to stand state */ crouchingState = G_IsCrouched(ent) ? 1 : 0; if (!crouchingState) { G_MoveCalc(visTeam, ent, ent->pos, crouchingState, ent->TU); length = gi.MoveLength(level.pathingMap, to, crouchingState, false); autoCrouchRequired = true; } } } /* this let the footstep sounds play even over network */ ent->think = G_PhysicsStep; ent->nextthink = level.time; /* assemble dvec-encoded move data */ VectorCopy(to, pos); initTU = ent->TU; numdv = G_FillDirectionTable(dvtab, lengthof(dvtab), crouchingState, pos); /* make sure to end any other pending events - we rely on EV_ACTOR_MOVE not being active anymore */ gi.EndEvents(); /* everything ok, found valid route? */ if (VectorCompare(pos, ent->pos)) { byte* stepAmount = NULL; int usedTUs = 0; /* no floor inventory at this point */ FLOOR(ent) = NULL; while (numdv > 0) { /* A flag to see if we needed to change crouch state */ int crouchFlag; const byte oldDir = ent->dir; int dvec; /* get next dvec */ numdv--; dvec = dvtab[numdv]; /* This is the direction to make the step into */ dir = getDVdir(dvec); /* turn around first */ status = G_ActorDoTurn(ent, dir); if (status & VIS_STOP) { autoCrouchRequired = false; if (ent->moveinfo.steps == 0) usedTUs += TU_TURN; break; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; /* if something appears on our route that didn't trigger a VIS_STOP, we have to * send the turn event if this is our first step */ if (oldDir != ent->dir && ent->moveinfo.steps == 0) { G_EventActorTurn(ent); usedTUs += TU_TURN; } break; } /* decrease TUs */ div = gi.GetTUsForDirection(dir, G_IsCrouched(ent)); if ((int) (usedTUs + div) > ent->TU) break; usedTUs += div; /* This is now a flag to indicate a change in crouching - we need this for * the stop in mid move call(s), because we need the updated entity position */ crouchFlag = 0; /* Calculate the new position after the decrease in TUs, otherwise the game * remembers the false position if the time runs out */ PosAddDV(ent->pos, crouchFlag, dvec); /* slower if crouched */ if (G_IsCrouched(ent)) ent->speed = ACTOR_SPEED_CROUCHED; else ent->speed = ACTOR_SPEED_NORMAL; ent->speed *= g_actorspeed->value; if (crouchFlag == 0) { /* No change in crouch */ edict_t* clientAction; int contentFlags; vec3_t pointTrace; G_EdictCalcOrigin(ent); VectorCopy(ent->origin, pointTrace); pointTrace[2] += PLAYER_MIN; contentFlags = gi.PointContents(pointTrace); /* link it at new position - this must be done for every edict * movement - to let the server know about it. */ gi.LinkEdict(ent); /* Only the PHALANX team has these stats right now. */ if (ent->chr.scoreMission) { float truediv = gi.GetTUsForDirection(dir, 0); /* regardless of crouching ! */ if (G_IsCrouched(ent)) ent->chr.scoreMission->movedCrouched += truediv; else ent->chr.scoreMission->movedNormal += truediv; } /* write the step to the net */ G_WriteStep(ent, &stepAmount, dvec, contentFlags); /* check if player appears/perishes, seen from other teams */ G_CheckVis(ent, true); /* check for anything appearing, seen by "the moving one" */ status = G_CheckVisTeamAll(ent->team, false, ent); /* Set ent->TU because the reaction code relies on ent->TU being accurate. */ G_ActorSetTU(ent, initTU - usedTUs); clientAction = ent->clientAction; oldState = ent->state; oldHP = ent->HP; /* check triggers at new position */ if (G_TouchTriggers(ent)) { if (!clientAction) status |= VIS_STOP; } G_TouchSolids(ent, 10.0f); /* state has changed - maybe we walked on a trigger_hurt */ if (oldState != ent->state) status |= VIS_STOP; else if (oldHP != ent->HP) status |= VIS_STOP; } else if (crouchFlag == 1) { /* Actor is standing */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } else if (crouchFlag == -1) { /* Actor is crouching and should stand up */ G_ClientStateChange(player, ent, STATE_CROUCHED, false); } /* check for reaction fire */ if (G_ReactionFireOnMovement(ent)) { status |= VIS_STOP; autoCrouchRequired = false; } /* check for death */ if (((oldHP != 0 && oldHP != ent->HP) || (oldState != ent->state)) && !G_IsDazed(ent)) { /** @todo Handle dazed via trigger_hurt */ /* maybe this was due to rf - then the G_ActorDie was already called */ if (!G_IsDead(ent)) { G_CheckDeathOrKnockout(ent, NULL, NULL, oldHP - ent->HP); } return; } if (G_ActorShouldStopInMidMove(ent, status, dvtab, numdv - 1)) { /* don't autocrouch if new enemy becomes visible */ autoCrouchRequired = false; break; } /* Restore ent->TU because the movement code relies on it not being modified! */ G_ActorSetTU(ent, initTU); } /* submit the TUs / round down */ if (g_notu != NULL && g_notu->integer) G_ActorSetTU(ent, initTU); else G_ActorSetTU(ent, initTU - usedTUs); G_SendStats(ent); /* end the move */ G_GetFloorItems(ent); gi.EndEvents(); } if (autoCrouchRequired) { /* toggle back to crouched state */ G_ClientStateChange(player, ent, STATE_CROUCHED, true); } }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (Edict* self) { if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { self->link = G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = nullptr; } Edict* chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const Item* ic; G_GetFloorItems(chain); ic = chain->getFloor(); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->getNext()) { const objDef_t* od = ic->def(); assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { const int endTime = level.actualRound - chain->count; const int spawnIndex = (self->getTeam() + level.teamOfs) % MAX_TEAMS; const int currentIndex = (level.activeTeam + level.teamOfs) % MAX_TEAMS; /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || endTime < chain->time || (endTime == level.actualRound && spawnIndex <= currentIndex)) return; } /* not destroyed yet */ if ((chain->flags & FL_DESTROYABLE) && chain->HP) return; } chain = chain->groupChain; } if (self->use) self->use(self, nullptr); /* store team before the edict is released */ const int team = self->getTeam(); chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->item != nullptr) { Edict* item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != nullptr) { if (!G_InventoryRemoveItemByID(chain->item, item, CID_FLOOR)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->getIdNum()); } else if (!item->getFloor()) { G_EventPerish(*item); G_FreeEdict(item); } } } if (chain->link != nullptr) { Edict* particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != nullptr) { G_AppearPerishEvent(PM_ALL, false, *particle, nullptr); G_FreeEdict(particle); } chain->link = nullptr; } Edict* ent = chain->groupChain; /* free the trigger */ if (chain->child()) G_FreeEdict(chain->child()); /* free the group chain */ G_FreeEdict(chain); chain = ent; } self = nullptr; /* still active mission edicts left */ Edict* ent = nullptr; while ((ent = G_EdictsGetNextInUse(ent))) if (ent->type == ET_MISSION && ent->getTeam() == team) return; G_MatchEndTrigger(team, 10); }
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 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 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); }
/** * @note Think functions are only executed when the match is running * or in other word, the game has started */ void G_MissionThink (edict_t *self) { edict_t *chain = self->groupMaster; edict_t *ent; int team; if (!G_MatchIsRunning()) return; /* when every player has joined the match - spawn the mission target * particle (if given) to mark the trigger */ if (self->particle) { G_SpawnParticle(self->origin, self->spawnflags, self->particle); /* This is automatically freed on map shutdown */ self->particle = NULL; } if (!chain) chain = self; while (chain) { if (chain->type == ET_MISSION) { if (chain->item) { const invList_t *ic; G_GetFloorItems(chain); ic = FLOOR(chain); if (!ic) { /* reset the counter if there is no item */ chain->count = 0; return; } for (; ic; ic = ic->next) { const objDef_t *od = ic->item.t; assert(od); /* not the item we are looking for */ if (Q_streq(od->id, chain->item)) break; } if (!ic) { /* reset the counter if it's not the searched item */ chain->count = 0; return; } } if (chain->time) { /* not every edict in the group chain has * been occupied long enough */ if (!chain->count || level.actualRound - chain->count < chain->time) return; } /* not destroyed yet */ if ((chain->flags & FL_DESTROYABLE) && chain->HP) return; } chain = chain->groupChain; } if (self->use) self->use(self, NULL); /* store team before the edict is released */ team = self->team; chain = self->groupMaster; if (!chain) chain = self; while (chain) { if (chain->item != NULL) { edict_t *item = G_GetEdictFromPos(chain->pos, ET_ITEM); if (item != NULL) { if (!G_InventoryRemoveItemByID(chain->item, item, gi.csi->idFloor)) { Com_Printf("Could not remove item '%s' from floor edict %i\n", chain->item, item->number); } else { G_AppearPerishEvent(G_VisToPM(item->visflags), false, item, NULL); } } } if (chain->particle != NULL) { /** @todo not yet working - particle stays active */ edict_t *particle = G_GetEdictFromPos(chain->pos, ET_PARTICLE); if (particle != NULL) { G_AppearPerishEvent(PM_ALL, false, particle, NULL); G_FreeEdict(particle); } } ent = chain->groupChain; /* free the trigger */ if (chain->child) G_FreeEdict(chain->child); /* free the group chain */ G_FreeEdict(chain); chain = ent; } self = NULL; /* still active mission edicts left */ ent = NULL; while ((ent = G_EdictsGetNextInUse(ent))) if (ent->type == ET_MISSION && ent->team == team) return; G_MatchEndTrigger(team, 10); }