/** * @brief This is called after the actors are spawned and will set actor states without consuming TUs * @param player The player to perform the action for */ void G_ClientInitActorStates (const player_t * player) { const int length = gi.ReadByte(); /* Get the actor amount that the client sent. */ int i; for (i = 0; i < length; i++) { const int ucn = gi.ReadShort(); int saveTU; actorHands_t hand; int fmIdx, objIdx; edict_t *ent = G_EdictsGetActorByUCN(ucn, player->pers.team); if (!ent) gi.Error("Could not find character on team %i with unique character number %i", player->pers.team, ucn); /* these state changes are not consuming any TUs */ saveTU = ent->TU; G_ClientStateChange(player, ent, gi.ReadShort(), false); hand = (actorHands_t)gi.ReadShort(); fmIdx = gi.ReadShort(); objIdx = gi.ReadShort(); G_ActorSetTU(ent, saveTU); if (objIdx != NONE) { G_ReactionFireSettingsUpdate(ent, fmIdx, hand, INVSH_GetItemByIDX(objIdx)); } G_ClientStateChangeUpdate(ent); } }
/** * @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 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; }