1 /**
2 * @file
3 */
4
5 /*
6 Copyright (C) 2002-2013 UFO: Alien Invasion.
7
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License
10 as published by the Free Software Foundation; either version 2
11 of the License, or (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22
23 */
24
25 #include "g_inventory.h"
26 #include "g_client.h"
27 #include "g_spawn.h"
28 #include "g_utils.h"
29 #include "g_vis.h"
30
G_GetEquipDefByID(const char * equipID)31 const equipDef_t* G_GetEquipDefByID (const char* equipID)
32 {
33 int i;
34 const equipDef_t* ed;
35
36 for (i = 0, ed = gi.csi->eds; i < gi.csi->numEDs; i++, ed++)
37 if (Q_streq(equipID, ed->id))
38 return ed;
39
40 gi.DPrintf("Could not find the equipment with the id: '%s'\n", equipID);
41 return nullptr;
42 }
43
44 /**
45 * @brief Callback to G_GetEdictFromPos() for given position, used to get items from position.
46 * @param[in] pos A position for which items are wanted.
47 * @sa G_GetFloorItems
48 */
G_GetFloorItemFromPos(const pos3_t pos)49 Edict* G_GetFloorItemFromPos (const pos3_t pos)
50 {
51 return G_GetEdictFromPos(pos, ET_ITEM);
52 }
53
54 /**
55 * @brief Prepares a list of items on the floor at given entity position.
56 * @param[in] ent Pointer to an entity being an actor.
57 * @return pointer to Edict being a floor (with items) or @c nullptr in case no items were found
58 * on the edict grid position.
59 */
G_GetFloorItems(Edict * ent)60 Edict* G_GetFloorItems (Edict* ent)
61 {
62 Edict* floor = G_GetFloorItemFromPos(ent->pos);
63 /* found items */
64 if (floor) {
65 ent->setFloor(floor);
66 return floor;
67 }
68
69 /* no items on ground found */
70 ent->resetFloor();
71 return nullptr;
72 }
73
74 /**
75 * @brief Removes one particular item from a given container
76 * @param itemID The id of the item to remove
77 * @param ent The edict that holds the inventory to remove the item from
78 * @param container The container in the inventory of the edict to remove the searched item from.
79 * @return @c true if the removal was successful, @c false otherwise.
80 */
G_InventoryRemoveItemByID(const char * itemID,Edict * ent,containerIndex_t container)81 bool G_InventoryRemoveItemByID (const char* itemID, Edict* ent, containerIndex_t container)
82 {
83 Item* ic = ent->getContainer(container);
84 while (ic) {
85 const objDef_t* item = ic->def();
86 if (item != nullptr && Q_streq(item->id, itemID)) {
87 /* remove the virtual item to update the inventory lists */
88 if (!game.i.removeFromInventory(&ent->chr.inv, INVDEF(container), ic))
89 gi.Error("Could not remove item '%s' from inventory %i",
90 ic->def()->id, container);
91 G_EventInventoryDelete(*ent, G_VisToPM(ent->visflags), container, ic->getX(), ic->getY());
92 return true;
93 }
94 ic = ic->getNext();
95 }
96
97 return false;
98 }
99
100 /**
101 * @brief Checks whether the given container contains items that should be
102 * dropped to the floor. Also removes virtual items.
103 * @param[in,out] ent The entity to check the inventory containers for. The inventory of
104 * this edict is modified in this function (the virtual items are removed).
105 * @param[in] container The container of the entity inventory to check
106 * @return @c true if there are items that should be dropped to floor, @c false otherwise
107 */
G_InventoryDropToFloorCheck(Edict * ent,containerIndex_t container)108 static bool G_InventoryDropToFloorCheck (Edict* ent, containerIndex_t container)
109 {
110 if (container == CID_ARMOUR || container == CID_IMPLANT)
111 return false;
112
113 Item* ic = ent->getContainer(container);
114 if (!ic)
115 return false;
116
117 bool check = false;
118 while (ic) {
119 assert(ic->def());
120 if (ic->def()->isVirtual) {
121 Item* next = ic->getNext();
122 /* remove the virtual item to update the inventory lists */
123 if (!game.i.removeFromInventory(&ent->chr.inv, INVDEF(container), ic))
124 gi.Error("Could not remove virtual item '%s' from inventory %i",
125 ic->def()->id, container);
126 ic = next;
127 } else {
128 /* there are none virtual items left that should be send to the client */
129 check = true;
130 ic = ic->getNext();
131 }
132 }
133 return check;
134 }
135
136 /**
137 * @brief Adds a new item to an existing or new floor container edict at the given grid location
138 * @param pos The grid location to spawn the item on the floor
139 * @param itemID The item to spawn
140 */
G_AddItemToFloor(const pos3_t pos,const char * itemID)141 bool G_AddItemToFloor (const pos3_t pos, const char* itemID)
142 {
143 Edict* floor;
144 const objDef_t* od = INVSH_GetItemByIDSilent(itemID);
145 if (!od) {
146 gi.DPrintf("Could not find item '%s'\n", itemID);
147 return false;
148 }
149
150 /* Also sets FLOOR(ent) to correct value. */
151 floor = G_GetFloorItemFromPos(pos);
152 /* nothing on the ground yet? */
153 if (!floor)
154 floor = G_SpawnFloor(pos);
155
156 Item item(od);
157 return game.i.tryAddToInventory(&floor->chr.inv, &item, INVDEF(CID_FLOOR));
158 }
159
160 /** @brief Move items to adjacent locations if the containers on the current
161 * floor edict are full */
162 /* #define ADJACENT */
163
164 #ifdef ADJACENT
G_InventoryPlaceItemAdjacent(Edict * ent)165 static bool G_InventoryPlaceItemAdjacent (Edict* ent)
166 {
167 vec2_t oldPos; /* if we have to place it to adjacent */
168 Edict* floorAdjacent;
169 int i;
170
171 Vector2Copy(ent->pos, oldPos);
172 floorAdjacent = nullptr;
173
174 for (i = 0; i < DIRECTIONS; i++) {
175 /** @todo Check whether movement is possible here - otherwise don't use this field */
176 /* extend pos with the direction vectors */
177 /** @todo Don't know why the adjacent stuff has been disabled, but if it was buggy, it's probably */
178 /** because the third ent->pos in the next line should be pos[1] ?!. (Duke, 13.1.11) */
179 Vector2Set(ent->pos, ent->pos[0] + dvecs[i][0], ent->pos[0] + dvecs[i][1]);
180 /* now try to get a floor entity for that new location */
181 floorAdjacent = G_GetFloorItems(ent);
182 if (!floorAdjacent) {
183 floorAdjacent = G_SpawnFloor(ent->pos);
184 } else {
185 /* destroy this edict (send this event to all clients that see the edict) */
186 G_EventPerish(*floorAdjacent);
187 G_VisFlagsReset(*floorAdjacent);
188 }
189
190 Item* ic = nullptr;
191 int x, y;
192 floorAdjacent->chr.inv.findSpace(INVDEF(CID_FLOOR), ic, &x, &y, ic);
193 if (x != NONE) {
194 ic->setX(x);
195 ic->setY(y);
196 ic->setNext(floorAdjacent->getFloor());
197 floorAdjacent->chr.inv.setFloorContainer(ic);
198 break;
199 }
200 /* restore original pos */
201 Vector2Copy(oldPos, ent->pos);
202 }
203
204 /* added to adjacent pos? */
205 if (i < DIRECTIONS) {
206 /* restore original pos - if no free space, this was done
207 * already in the for loop */
208 Vector2Copy(oldPos, ent->pos);
209 return false;
210 }
211
212 if (floorAdjacent)
213 G_CheckVis(floorAdjacent, true);
214
215 return true;
216 }
217 #endif
218
219 /**
220 * @brief Move the whole given inventory to the floor and destroy the items that do not fit there.
221 * @param[in] ent Pointer to an Edict being an actor.
222 * @sa G_ActorDie
223 */
G_InventoryToFloor(Edict * ent)224 void G_InventoryToFloor (Edict* ent)
225 {
226 /* check for items that should be dropped */
227 /* ignore items linked from any temp container */
228 const Container* cont = nullptr;
229 while ((cont = ent->chr.inv.getNextCont(cont))) {
230 if (G_InventoryDropToFloorCheck(ent, cont->id))
231 break; /* found at least one item */
232 }
233
234 /* edict is not carrying any items */
235 if (!cont)
236 return;
237
238 /* find the floor */
239 Edict* floor = G_GetFloorItems(ent);
240 if (!floor) {
241 floor = G_SpawnFloor(ent->pos);
242 } else {
243 /* destroy this edict (send this event to all clients that see the edict) */
244 G_EventPerish(*floor);
245 G_VisFlagsReset(*floor);
246 }
247
248 /* drop items */
249 /* cycle through all containers */
250 containerIndex_t container;
251 for (container = 0; container < CID_MAX; container++) {
252 /* skip floor - we want to drop to floor */
253 if (container == CID_FLOOR)
254 continue;
255
256 /* skip CID_ARMOUR, we will collect armours using armour container,
257 * not CID_FLOOR */
258 if (container == CID_ARMOUR || container == CID_IMPLANT)
259 continue;
260
261 /* now cycle through all items for the container of the character (or the entity) */
262 Item* ic, *next;
263 for (ic = ent->getContainer(container); ic; ic = next) {
264 /* Save the next inv-list before it gets overwritten below.
265 * Do not put this in the "for" statement,
266 * unless you want an endless loop. ;) */
267 next = ic->getNext();
268 Item item = *ic;
269
270 /* only floor can summarize, so everything on the actor must have amount=1 */
271 assert(item.getAmount() == 1);
272 if (!game.i.removeFromInventory(&ent->chr.inv, INVDEF(container), ic))
273 gi.Error("Could not remove item '%s' from inventory %i of entity %i",
274 ic->def()->id, container, ent->number);
275 if (game.i.addToInventory(&floor->chr.inv, &item, INVDEF(CID_FLOOR), NONE, NONE, 1) == nullptr)
276 gi.Error("Could not add item '%s' from inventory %i of entity %i to floor container",
277 ic->def()->id, container, ent->number);
278 #ifdef ADJACENT
279 G_InventoryPlaceItemAdjacent(ent);
280 #endif
281 }
282 /* destroy link */
283 ent->resetContainer(container);
284 }
285
286 ent->setFloor(floor);
287
288 /* send item info to the clients */
289 G_CheckVis(floor);
290 }
291
292 /**
293 * @brief Read item from the network buffer
294 * @param[in,out] item The Item being send through net.
295 * @param[in,out] container Container which is being updated with item sent.
296 * @param[in] x Position of item in given container.
297 * @param[in] y Position of item in given container.
298 * @sa CL_NetReceiveItem
299 * @sa EV_INV_TRANSFER
300 */
G_ReadItem(Item * item,const invDef_t ** container,int * x,int * y)301 void G_ReadItem (Item* item, const invDef_t** container, int* x, int* y)
302 {
303 int t, m;
304 int ammoleft;
305 int amount;
306 containerIndex_t containerID;
307
308 gi.ReadFormat("sbsbbbbs", &t, &ammoleft, &m, &containerID, x, y, &item->rotated, &amount);
309 item->setAmmoLeft(ammoleft);
310 item->setAmount(amount);
311
312 if (t < 0 || t >= gi.csi->numODs)
313 gi.Error("Item index out of bounds: %i", t);
314 item->setDef(&gi.csi->ods[t]);
315
316 if (m != NONE) {
317 if (m < 0 || m >= gi.csi->numODs)
318 gi.Error("Ammo index out of bounds: %i", m);
319 item->setAmmoDef(&gi.csi->ods[m]);
320 } else {
321 item->setAmmoDef(nullptr);
322 }
323
324 if (isValidContId(containerID))
325 *container = INVDEF(containerID);
326 else
327 gi.Error("container id is out of bounds: %i", containerID);
328 }
329
330 /**
331 * @brief Write an item to the network buffer
332 * @param[in,out] item The Item being send through net.
333 * @param[in,out] contId Container which is being updated with item sent.
334 * @param[in] x Position of item in given container.
335 * @param[in] y Position of item in given container.
336 * @sa CL_NetReceiveItem
337 * @sa EV_INV_TRANSFER
338 */
G_WriteItem(const Item & item,const containerIndex_t contId,int x,int y)339 void G_WriteItem (const Item &item, const containerIndex_t contId, int x, int y)
340 {
341 assert(item.def());
342 gi.WriteFormat("sbsbbbbs", item.def()->idx, item.getAmmoLeft(), item.ammoDef() ? item.ammoDef()->idx : NONE, contId, x, y, item.rotated, item.getAmount());
343 }
344
345 /**
346 * @brief Sends whole inventory through the network buffer.
347 * @param[in] playerMask The player mask to determine which clients should receive the event (@c G_VisToPM(ent->visflags)).
348 * @param[in] ent Pointer to an actor or floor container with inventory to send.
349 * @sa G_AppearPerishEvent
350 * @sa CL_InvAdd
351 */
G_SendInventory(playermask_t playerMask,const Edict & ent)352 void G_SendInventory (playermask_t playerMask, const Edict &ent)
353 {
354 int nr = 0;
355
356 /* test for pointless player mask */
357 if (!playerMask)
358 return;
359
360 const Container* cont = nullptr;
361 while ((cont = ent.chr.inv.getNextCont(cont, true))) {
362 if (!G_IsItem(&ent) && INVDEF(cont->id)->temp)
363 continue;
364 nr += cont->countItems();
365 }
366
367 /* return if no inventory items to send */
368 if (nr == 0)
369 return;
370
371 G_EventInventoryAdd(ent, playerMask, nr);
372 cont = nullptr;
373 while ((cont = ent.chr.inv.getNextCont(cont, true))) {
374 if (!G_IsItem(&ent) && INVDEF(cont->id)->temp)
375 continue;
376 Item* item = nullptr;
377 while ((item = cont->getNextItem(item))) {
378 /* send a single item */
379 assert(item->def());
380 G_WriteItem(*item, cont->id, item->getX(), item->getY());
381 }
382 }
383 G_EventEnd();
384 }
385