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