1 /*
2 *
3 * Copyright (c) 2002, 2003 Johannes Prix
4 * Copyright (c) 2004-2010 Arthur Huillet
5 *
6 *
7 * This file is part of Freedroid
8 *
9 * Freedroid is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * Freedroid is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Freedroid; see the file COPYING. If not, write to the
21 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
22 * MA 02111-1307 USA
23 *
24 */
25
26 /**
27 * This file contains all the functions managing the items in the game.
28 */
29
30 #define _items_c
31
32 #include "system.h"
33
34 #include "defs.h"
35 #include "struct.h"
36 #include "global.h"
37 #include "proto.h"
38
39 /**
40 * \brief Initializes an empty item.
41 * \param item_ Item.
42 */
init_item(item * it)43 void init_item(item *it)
44 {
45 memset(it, 0, sizeof(item));
46 it->type = -1;
47 it->pos.x = -1;
48 it->pos.y = -1;
49 it->pos.z = -1;
50 it->inventory_position.x = -1;
51 it->inventory_position.y = -1;
52 }
53
create_item_with_id(const char * item_id,int full_durability,int multiplicity)54 item create_item_with_id(const char *item_id, int full_durability, int multiplicity)
55 {
56 item new_item;
57
58 init_item(&new_item);
59 new_item.type = get_item_type_by_id(item_id);
60 if (new_item.type < 0 || new_item.type >= Number_Of_Item_Types) {
61 error_message(__FUNCTION__, "No items with the name \"%s\" exist in the game. Cannot create item.\nChanging item type to bug item to avoid crash.", PLEASE_INFORM, item_id);
62 new_item.type = 0;
63 }
64
65 FillInItemProperties(&new_item, full_durability, multiplicity);
66
67 return new_item;
68 }
69
70 /**
71 * This function checks if the item can be equipped
72 * \param it item to check
73 * \return TRUE if the item can be installed in one slot, FALSE otherwise
74 */
equippable_item(item * it)75 static int equippable_item(item *it)
76 {
77 return ItemMap[it->type].slot != NO_SLOT;
78 }
79
equip_item(item * new_item)80 void equip_item(item *new_item)
81 {
82 item *old_item;
83 itemspec *new_itemspec;
84
85 new_itemspec = &ItemMap[new_item->type];
86
87 // If the item can't be equipped, stop now and throw a warning.
88 if (!equippable_item(new_item)) {
89 error_message(__FUNCTION__, "Tried to equip the item \"%s\" which can't be equipped.",
90 PLEASE_INFORM, new_itemspec->id);
91 return;
92 }
93
94 // If there's an existing item in the equipment slot, drop it to the
95 // inventory or to the floor.
96 old_item = get_equipped_item_in_slot_for(new_item->type);
97 if (old_item->type != -1) {
98 give_item(old_item);
99 }
100
101 // If we're equipping a two-handed weapon, we need to unequip the shield
102 // as well. We drop the shield to the inventory or to the floor.
103 if (new_itemspec->weapon_needs_two_hands && Me.shield_item.type != -1) {
104 give_item(&Me.shield_item);
105 }
106
107 // Before equipping a shield, if a two-handed weapon is equipped, you need to drop it.
108 if (new_itemspec->slot == SHIELD_SLOT && Me.weapon_item.type != -1
109 && ItemMap[Me.weapon_item.type].weapon_needs_two_hands) {
110 give_item(&Me.weapon_item);
111 }
112
113 // Move the new item to the now empty equipment slot.
114 MoveItem(new_item, old_item);
115 }
116
117
118 /**
119 * Gets a pointer to the currently equipped item of the
120 * specified type
121 */
get_equipped_item_in_slot_for(int item_type)122 item *get_equipped_item_in_slot_for(int item_type)
123 {
124 item *equipped_item;
125 itemspec *spec;
126
127 spec = &ItemMap[item_type];
128
129 if (spec->slot == WEAPON_SLOT) {
130 equipped_item = &Me.weapon_item;
131 }
132 else if (spec->slot == BOOT_SLOT) {
133 equipped_item = &Me.drive_item;
134 }
135 else if (spec->slot == ARMOR_SLOT) {
136 equipped_item = &Me.armour_item;
137 }
138 else if (spec->slot == SHIELD_SLOT) {
139 equipped_item = &Me.shield_item;
140 }
141 else if (spec->slot == HELM_SLOT) {
142 equipped_item = &Me.special_item;
143 }
144 else
145 {
146 equipped_item = NULL;
147 }
148
149 return equipped_item;
150 }
151
152 /**
153 * This function does the home made item repair, i.e. the item repair via
154 * the repair skill in contrast to the item repair via the shop, which of
155 * course works much better.
156 */
self_repair_item(item * it)157 static void self_repair_item(item *it)
158 {
159 int wear = 0, used = 0;
160 int my_skill_level = 0;
161 int percent_chance = MyRandom(100);
162
163 if (it->max_durability == -1) {
164 play_sound("effects/tux_ingame_comments/Tux_Item_Cant_Be_0.ogg");
165 return;
166 }
167
168 my_skill_level = Me.skill_level[get_program_index_with_name("Repair equipment")];
169
170 /* if Tux's repair ability is at the full skill level
171 * make it so that most of the time the item is fully repaired. Give it a 5 % chance
172 * of improving the item and a 3% chance of making it worse.
173 * otherwise give him a skill level *10 percent chance of repairing the item completely
174 */
175 if(my_skill_level >= NUMBER_OF_SKILL_LEVELS-1) {
176 if(percent_chance > 94)
177 {
178 it->max_durability++;
179 }
180 else if (percent_chance < 3)
181 {
182 it->max_durability--;
183 }
184 }
185 else if(my_skill_level*10 < percent_chance) {
186 used = it->max_durability - it->current_durability;
187 /* Self repair formula: decrease max_durability between 1 and 11-skill_level*/
188 wear = 1 + MyRandom(10 - my_skill_level);
189 //never decrease more than current missing durability
190 it->max_durability -= min(wear, used);
191 if (it->max_durability < 1) {
192 it->max_durability = 1;
193 }
194 }
195 //when you wear off all extra durability, the item become normal again
196 if (it->quality == GOOD_QUALITY && it->max_durability < ItemMap[it->type].base_item_durability) {
197 it->quality = NORMAL_QUALITY;
198 }
199 //if you add extra durability, the item becomes good
200 if (it->quality == NORMAL_QUALITY && it->max_durability > ItemMap[it->type].base_item_durability) {
201 it->quality = GOOD_QUALITY;
202 }
203
204 it->current_durability = it->max_durability;
205 play_sound("effects/tux_ingame_comments/Tux_This_Quick_Fix_0.ogg");
206 }
207
208 /**
209 * This function calculates the price of a single given item, taking into account
210 * (*) the base list price of the item
211 * (*) the base list prices of the installed add-ons
212 */
calculate_item_buy_price(item * BuyItem)213 unsigned long calculate_item_buy_price(item * BuyItem)
214 {
215 int i;
216 int price = ItemMap[BuyItem->type].base_list_price;
217
218 // Add the prices of the add-ons to the total price.
219 for (i = 0; i < BuyItem->upgrade_sockets.size; i++) {
220 const char *addon = BuyItem->upgrade_sockets.arr[i].addon;
221 if (addon) {
222 int type = get_item_type_by_id(addon);
223 price += ItemMap[type].base_list_price;
224 }
225 }
226
227 return price;
228 }
229
230 /**
231 * This function calculates the sell price of a single given item, taking into account
232 * the markdown (currently 0.3 for all NPCs)
233 */
calculate_item_sell_price(item * BuyItem)234 unsigned long calculate_item_sell_price(item * BuyItem)
235 {
236 int price;
237 // Some items cannot be sold
238 if (!(price = calculate_item_buy_price(BuyItem)))
239 return 0;
240
241 // Items sell for less than the full price of the item.
242 price = floor(0.3 * price);
243
244 // Prices have to be non-zero so the item can be sold
245 return price ? price : 1;
246 }
247
248 /**
249 * This function calculates the price of a given item, taking into account
250 * (*) the base list price of the item
251 * (*) the base list prices of the installed add-ons
252 * (*) AND THE CURRENT DURABILITY of the item in relation to its max durability.
253 */
calculate_item_repair_price(item * repair_item)254 unsigned long calculate_item_repair_price(item * repair_item)
255 {
256 // For repair, it's not the full 'buy' cost...
257 //
258 #define REPAIR_PRICE_FACTOR (0.5)
259
260 // This is the price of the DAMAGE in the item, haha
261 // This can only be requested for repair items
262 //
263 if (repair_item->max_durability != (-1)) {
264 unsigned long price = (calculate_item_buy_price(repair_item) *
265 REPAIR_PRICE_FACTOR * (repair_item->max_durability - repair_item->current_durability) / repair_item->max_durability);
266
267 // Never repair for free, minimum price is 1
268 return price ? price : 1;
269 }
270 return 0;
271 }; // long calculate_item_repair_price ( item* repair_item )
272
273 /**
274 * \brief Returns a random quality multiplier.
275 * \return a quality indicator.
276 */
random_item_quality()277 static enum item_quality random_item_quality()
278 {
279 // In order to make normal quality items more common than others, we first
280 // choose a quality level by indexing a probability distribution array.
281 const enum item_quality quality_distribution[] = {
282 BAD_QUALITY,
283 BAD_QUALITY, //20%
284 NORMAL_QUALITY,
285 NORMAL_QUALITY,
286 NORMAL_QUALITY,
287 NORMAL_QUALITY,
288 NORMAL_QUALITY,
289 NORMAL_QUALITY,
290 NORMAL_QUALITY, //70%
291 GOOD_QUALITY}; //10%
292 int max_index = (sizeof(quality_distribution) / sizeof(quality_distribution[0])) - 1;
293 int quality_index = MyRandom(max_index);
294 return quality_distribution[quality_index];
295 }
296
297 /**
298 * \brief Initializes the properties of the item.
299 *
300 * Sets the durability, armor rating, and other such properties based on the
301 * type of the item. The caller must ensure that the type field has been set.
302 *
303 * \param it Item whose properties to initialize.
304 * \param full_durability TRUE to make the item fully repaired.
305 * \param multiplicity Multiplicity of the item.
306 */
FillInItemProperties(item * it,int full_durability,int multiplicity)307 void FillInItemProperties(item *it, int full_durability, int multiplicity)
308 {
309 // Some basic error checking of item type.
310 if ((it->type < 0) || (it->type >= Number_Of_Item_Types)) {
311 error_message(__FUNCTION__, "Cannot fill in information for item with invalid type.", PLEASE_INFORM | IS_FATAL);
312 return;
313 }
314
315 itemspec *spec = &ItemMap[it->type];
316
317 it->multiplicity = multiplicity;
318 it->ammo_clip = 0;
319 it->throw_time = 0;
320 it->quality = NORMAL_QUALITY;
321 it->max_durability = -1;
322
323 if (!equippable_item(it)) {
324 return;
325 }
326
327 it->quality = random_item_quality();
328
329 // Add random bullets to the clip if the item is a gun.
330 if (spec->weapon_ammo_type && spec->weapon_ammo_clip_size) {
331 it->ammo_clip = MyRandom(spec->weapon_ammo_clip_size);
332 }
333
334 // Set the base damage reduction by using the item spec and a random multiplier.
335 it->armor_class = spec->base_armor_class + MyRandom(spec->armor_class_modifier);
336
337 // Set the maximum and current durabilities of the item.
338 if (spec->base_item_durability != -1) {
339 // The maximum durability is within the range specified by the item spec.
340 it->max_durability = spec->base_item_durability + MyRandom(spec->item_durability_modifier);
341
342 int half = it->max_durability / 2;
343 if (it->quality == BAD_QUALITY) {
344 //between 50% and 100%
345 it->max_durability = max(1, half + MyRandom(half));
346 } else if (it->quality == GOOD_QUALITY) {
347 //between 100% and 150%
348 it->max_durability += MyRandom(half);
349 }
350
351 if (full_durability) {
352 it->current_durability = it->max_durability;
353 } else {
354 int quarter = it->max_durability / 4;
355 //between 25% and 100%
356 it->current_durability = max(1, quarter + MyRandom(quarter*3));
357 }
358
359 } else {
360 it->max_durability = -1;
361 it->current_durability = 1;
362 }
363
364 // Calculate the item bonuses affected by add-ons.
365 // This will set the final armor rating and weapon damage, among other things.
366 calculate_item_bonuses(it);
367 }
368
append_item_name(item * ShowItem,struct auto_string * str)369 void append_item_name(item * ShowItem, struct auto_string *str)
370 {
371 // If the item has upgrade sockets, use a different color for the name.
372 if (ShowItem->upgrade_sockets.size) {
373 autostr_append(str, "%s", font_switchto_blue);
374 } else {
375 autostr_append(str, "%s", font_switchto_neon);
376 }
377
378 if (item_spec_eq_id(ShowItem->type, "Valuable Circuits"))
379 autostr_append(str, "%d ", ShowItem->multiplicity);
380
381 autostr_append(str, "%s", D_(item_specs_get_name(ShowItem->type)));
382
383 if (ShowItem->quality == GOOD_QUALITY) {
384 autostr_append(str, "\n%s", font_switchto_blue);
385 autostr_append(str, _("Good quality"));
386 } else if (ShowItem->quality == BAD_QUALITY) {
387 autostr_append(str, "\n%s", font_switchto_red);
388 autostr_append(str, _("Bad quality"));
389 }
390
391 // Now that the item name is out, we can switch back to the standard font color...
392 autostr_append(str, "%s", font_switchto_neon);
393 }
394
395 /**
396 * This function drops an item at a given place.
397 */
DropItemAt(int ItemType,int level_num,float x,float y,int multiplicity)398 item *DropItemAt(int ItemType, int level_num, float x, float y, int multiplicity)
399 {
400 gps item_pos;
401
402 if (ItemType < 0 || ItemType >= Number_Of_Item_Types)
403 error_message(__FUNCTION__, "\
404 Received item type %d that is outside the range of allowed item types.",
405 PLEASE_INFORM | IS_FATAL, ItemType);
406
407 // Fix virtual position (e.g. from a dying robot)
408 item_pos.x = x;
409 item_pos.y = y;
410 item_pos.z = level_num;
411 if (!resolve_virtual_position(&item_pos, &item_pos))
412 return NULL;
413
414 // Construct the new item
415 item tmp_item;
416 init_item(&tmp_item);
417 tmp_item.type = ItemType;
418 FillInItemProperties(&tmp_item, FALSE, multiplicity);
419
420 play_item_sound(ItemType, &item_pos);
421
422 return drop_item(&tmp_item, item_pos.x, item_pos.y, item_pos.z);
423 }
424
get_random_item_type(int class)425 static int get_random_item_type(int class)
426 {
427 int a = MyRandom(item_count_per_class[class] - 1) + 1;
428
429 //printf("Choosing in class %d among %d items, taking item %d\n", class, item_count_per_class[class], a);
430
431 int i;
432 for (i = 0; i < Number_Of_Item_Types; i++) {
433 if (ItemMap[i].min_drop_class != -1) {
434 if (ItemMap[i].min_drop_class <= class && ItemMap[i].max_drop_class >= class)
435 a--;
436 if (!a)
437 break;
438 }
439 }
440
441 if (a) {
442 error_message(__FUNCTION__, "Looking for random item with class %d, a = %d after the loop.", PLEASE_INFORM | IS_FATAL, class,
443 a);
444 }
445 //printf("Dropping item %s (%d <= class <= %d), class %d\n", ItemMap[i].item_name, ItemMap[i].min_drop_class, ItemMap[i].max_drop_class, class);
446 return i;
447 }
448
449 /**
450 * This function drops a random item to the floor of the current level
451 * at position ( x , y ).
452 *
453 * The strategy in dropping the item is that one can easily set up and
454 * modify the table of items to be dropped.
455 *
456 */
DropRandomItem(int level_num,float x,float y,int class,int force_magical)457 void DropRandomItem(int level_num, float x, float y, int class, int force_magical)
458 {
459 int DropDecision;
460 int drop_item_type = 1;
461 int drop_item_multiplicity = 1;
462
463 if (class == -1)
464 return; // -1 is the value to not drop item.
465
466 // Drop item only if the class is between 0 and MAX_DROP_CLASS.
467 if (class < 0 || class > MAX_DROP_CLASS) {
468 error_message(__FUNCTION__, "The drop class %d is invalid (out of 0 to MAX_DROP_CLASS: %d).\n", PLEASE_INFORM, class, MAX_DROP_CLASS);
469 return;
470 }
471
472 // First we determine if there is something dropped at all or not,
473 // cause in the later case, we can return immediately. If a drop is
474 // forced, we need not check for not do drop.
475 //
476 DropDecision = MyRandom(100);
477
478 // We decide whether we drop something at all or not
479 //
480 if ((DropDecision < 100 - GOLD_DROP_PERCENTAGE) && (DropDecision > ITEM_DROP_PERCENTAGE))
481 return;
482
483 // Perhaps it's some gold that will be dropped. That's rather
484 // simple, so we do this first.
485 //
486 if ((DropDecision > 100 - GOLD_DROP_PERCENTAGE)) {
487 // If class == 0, we want to avoid to drop 0-1 valuable circuits
488 int how_many = (class == 0) ? 2 : 0;
489 how_many += MONEY_PER_BOT_CLASS * class + MyRandom(MONEY_PER_BOT_CLASS - 1);
490 DropItemAt(get_item_type_by_id("Valuable Circuits"), level_num, x, y, how_many);
491 }
492
493 if ((DropDecision < ITEM_DROP_PERCENTAGE)) {
494 drop_item_type = get_random_item_type(class);
495
496 // Determine the multiplicity for the item
497 drop_item_multiplicity = ItemMap[drop_item_type].drop_amount + MyRandom(ItemMap[drop_item_type].drop_amount_max - ItemMap[drop_item_type].drop_amount);
498
499 // Create the item and place it to the map. This can fail under certain
500 // conditions so we need to check for errors and give up if one occurred.
501 item *it = DropItemAt(drop_item_type, level_num, x, y, drop_item_multiplicity);
502 if (!it) {
503 return;
504 }
505
506 // Create sockets occasionally if the item is of a customizable type.
507 int socket_drop_decision = MyRandom(100);
508 if (item_can_be_customized(it) && (force_magical ||
509 socket_drop_decision < SOCKET_DROP_PERCENTAGE)) {
510 // Decide how many sockets to create. We randomly index an array of
511 // socket counts to implement a non-uniform probability distribution.
512 const int create_count_array[] = { 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4 };
513 int random_index = MyRandom(10);
514 int create_count = create_count_array[random_index];
515
516 // Create the desired number of sockets of random types.
517 while (create_count--) {
518 int socket_type = MyRandom(UPGRADE_SOCKET_TYPE_UNIVERSAL);
519 create_upgrade_socket(it, socket_type, NULL);
520 }
521 }
522 }
523 }
524
525 /**
526 * When the influencer gets hit, all of his equipment suffers some damage.
527 * This is exactly what this function does: apply the damage.
528 */
DamageItem(item * CurItem)529 void DamageItem(item * CurItem)
530 {
531
532 // If the item mentioned as parameter exists and if it is of
533 // a destructable sort, then we apply the usual damage to it
534 if ((CurItem->type != (-1)) && (CurItem->max_durability != (-1))) {
535 CurItem->current_durability -= (MyRandom(100) < ARMOUR_DURABILITYLOSS_PERCENTAGE_WHEN_HIT) ? 1 : 0;
536
537 // Make sound denoting some protective item was damaged
538 BulletReflectedSound();
539
540 // If the item has gone over its threshold of durability, it finally
541 // breaks and vaporizes
542 //
543 if (rintf(CurItem->current_durability) <= 0) {
544 DeleteItem(CurItem);
545 }
546 }
547
548 }; // void DamageItem( item* CurItem )
549
550 /* Now we do the same for a weapon that has been fired */
DamageWeapon(item * CurItem)551 void DamageWeapon(item * CurItem)
552 {
553
554 if ((CurItem->type != (-1)) && (CurItem->max_durability != (-1))) {
555 CurItem->current_durability -= (MyRandom(100) < WEAPON_DURABILITYLOSS_PERCENTAGE_WHEN_USED) ? 1 : 0;
556 if (rintf(CurItem->current_durability) <= 0) {
557 DeleteItem(CurItem);
558 }
559 }
560
561 }; // void DamageWeapon( item* CurItem )
562
563 /**
564 * When the influencer gets hit, some of his equipment might suffer some damage.
565 * This is exactly what this function does: apply the damage.
566 */
DamageProtectiveEquipment()567 void DamageProtectiveEquipment()
568 {
569 int ItemHit = MyRandom(6);
570
571 if (ItemHit < 2)
572 DamageItem(&(Me.armour_item));
573 else if (ItemHit < 4)
574 DamageItem(&(Me.shield_item));
575 else if (ItemHit < 5)
576 DamageItem(&(Me.drive_item));
577 else
578 DamageItem(&(Me.special_item));
579
580 }; // void DamageProtectiveEquipment( void )
581
582 /**
583 * This function is used when an equipment EXCHANGE is performed, i.e.
584 * one weapon equipped is replaced by a new item using the mouse. This
585 * leads to an exchange in the items. Yet, if the new item came from
586 * inventory, the old item can't be just put in the same place where the
587 * new item was, cause it might be bigger. So, attempting to solve the
588 * problem, the old item from the slot can just be made into an item on
589 * the floor, but not visible yet of course, cause it still gets the
590 * held in hand attribute.
591 */
MakeHeldFloorItemOutOf(item * SourceItem)592 static void MakeHeldFloorItemOutOf(item * SourceItem)
593 {
594 int i;
595
596 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
597 if (CURLEVEL()->ItemList[i].type == (-1))
598 break;
599 }
600 if (i >= MAX_ITEMS_PER_LEVEL) {
601 DebugPrintf(0, "\n No free position to drop item!!! ");
602 i = 0;
603 Terminate(EXIT_FAILURE);
604 }
605 // Now we enter the item into the item list of this level
606 //
607 CopyItem(SourceItem, &(CURLEVEL()->ItemList[i]));
608
609 CURLEVEL()->ItemList[i].pos.x = Me.pos.x;
610 CURLEVEL()->ItemList[i].pos.y = Me.pos.y;
611 CURLEVEL()->ItemList[i].pos.z = Me.pos.z;
612
613 item_held_in_hand = &(CURLEVEL()->ItemList[i]);
614
615 DeleteItem(SourceItem);
616 }; // void MakeHeldFloorItemOutOf( item* SourceItem )
617
618 /**
619 * This function DELETES an item from the source location.
620 */
DeleteItem(item * it)621 void DeleteItem(item *it)
622 {
623 delete_upgrade_sockets(it);
624 init_item(it);
625 }
626
627 /**
628 * This function COPIES an item from the source location to the destination
629 * location.
630 */
CopyItem(item * SourceItem,item * DestItem)631 void CopyItem(item * SourceItem, item * DestItem)
632 {
633
634 memcpy(DestItem, SourceItem, sizeof(item));
635
636 // Create a soft copy of the upgrade sockets. Memcpy just copied the
637 // pointer but we want the actual data to be duplicated.
638 copy_upgrade_sockets(SourceItem, DestItem);
639 }
640
641 /**
642 * This function MOVES an item from the source location to the destination
643 * location. The source location is then marked as unused inventory
644 * entry.
645 */
MoveItem(item * source_item,item * dest_item)646 void MoveItem(item *source_item, item *dest_item)
647 {
648 if (source_item != dest_item) {
649 memcpy(dest_item, source_item, sizeof(item));
650 init_item(source_item);
651 }
652 }
653
654 /**
655 * This function applies a given item (to the influencer) and maybe
656 * eliminates the item after that, if it's an item that gets used up.
657 */
Quick_ApplyItem(int ItemKey)658 void Quick_ApplyItem(int ItemKey)
659 {
660 int FoundItemNr;
661
662 if (ItemKey == 0) {
663 //quick_inventory0 is slot 10
664 ItemKey = 10;
665 }
666
667 FoundItemNr = GetInventoryItemAt(ItemKey - 1, INVENTORY_GRID_HEIGHT - 1);
668 if (FoundItemNr == (-1))
669 return;
670
671 apply_item(&(Me.Inventory[FoundItemNr]));
672
673 }; // void Quick_ApplyItem( item* CurItem )
674
675 /**
676 * This function checks whether a given item has the name specified. This is
677 * used to match an item which its type in a flexible way (match by name instead
678 * of matching by index value)
679 */
get_item_type_by_id(const char * id)680 int get_item_type_by_id(const char *id)
681 {
682 int cidx = 0;
683 for (; cidx < Number_Of_Item_Types; cidx++) {
684 if (!strcmp(ItemMap[cidx].id, id))
685 return cidx;
686 }
687
688 error_message(__FUNCTION__, "Unable to find item id %s", PLEASE_INFORM, id);
689 return -1;
690 }
691
692 /**
693 * This function checks whether a given item has the name specified. This is
694 * used to match an item which its type in a flexible way (match by name instead
695 * of matching by index value)
696 */
item_spec_eq_id(int type,const char * id)697 int item_spec_eq_id(int type, const char *id)
698 {
699 if (type < 0 || type >= Number_Of_Item_Types)
700 return FALSE;
701
702 if (!strcmp(ItemMap[type].id, id))
703 return TRUE;
704 else
705 return FALSE;
706 }
707
708 /**
709 * This function applies a given item (to the influencer) and maybe
710 * eliminates the item after that, if it's an item that gets used up.
711 */
apply_item(item * CurItem)712 void apply_item(item * CurItem)
713 {
714 int failed_usage = 0; // if an item cannot be applied, to not remove it from inventory
715
716 // If the inventory slot is not at all filled, we need not do anything more...
717 if (CurItem->type < 0)
718 return;
719
720 if (!ItemMap[CurItem->type].right_use.tooltip) {
721 Me.TextVisibleTime = 0;
722 Me.TextToBeDisplayed = _("I can't use this item here.");
723 return;
724 }
725
726 // Forbid using items while paralyzed
727 if (Me.paralyze_duration) {
728 append_new_game_message(_("You can not use any items while paralyzed."));
729 return;
730 }
731
732 if (Me.busy_time > 0) {
733 char *msg;
734 switch (Me.busy_type) {
735 case DRINKING_POTION:
736 msg = _("You are drinking a potion!");
737 break;
738 case WEAPON_FIREWAIT:
739 msg = _("Your are waiting for your weapon to fire again!");
740 break;
741 case WEAPON_RELOAD:
742 msg = _("You are reloading your weapon!");
743 break;
744 case THROWING_GRENADE:
745 msg = _("You are throwing a grenade!");
746 break;
747 case RUNNING_PROGRAM:
748 msg = _("You are running a program!");
749 break;
750 case TAKING_PILL:
751 msg = _("You are taking a pill!");
752 break;
753 default:
754 msg = _("You are doing something so weird the game does not understand what it is");
755 }
756 // TRANSLATORS: the trailing %s is what the player is currently already doing (drinking, running a program, ...)
757 append_new_game_message(_("How do you expect to do two things at a time? %s"), msg);
758 return; //if the player is busy reloading or anything
759 }
760 // At this point we know that the item is applicable in combat situation
761 // and therefore all we need to do from here on is execute the item effect
762 // upon the influencer or his environment.
763 //
764 if (item_spec_eq_id(CurItem->type, "Barf's Energy Drink")) {
765 Me.energy += 15;
766 Me.temperature -= 15;
767 Me.running_power += 15;
768 } else if (item_spec_eq_id(CurItem->type, "Diet supplement")) {
769 Me.energy += 25;
770 play_sound("effects/new_healing_sound.ogg");
771 } else if (item_spec_eq_id(CurItem->type, "Antibiotic")) {
772 Me.energy += 50;
773 play_sound("effects/new_healing_sound.ogg");
774 } else if (item_spec_eq_id(CurItem->type, "Doc-in-a-can")) {
775 Me.energy += Me.maxenergy;
776 play_sound("effects/new_healing_sound.ogg");
777 } else if (item_spec_eq_id(CurItem->type, "Bottled ice")) {
778 Me.temperature -= 50;
779 } else if (item_spec_eq_id(CurItem->type, "Industrial coolant")) {
780 Me.temperature -= 100;
781 } else if (item_spec_eq_id(CurItem->type, "Liquid nitrogen")) {
782 Me.temperature = 0;
783 } else if (item_spec_eq_id(CurItem->type, "Running Power Capsule")) {
784 Me.running_power = Me.max_running_power;
785 Me.running_must_rest = FALSE;
786 } else if (item_spec_eq_id(CurItem->type, "Strength Capsule")) {
787 Me.current_power_bonus = 30;
788 Me.power_bonus_end_date = Me.current_game_date + 2.0 * 60;
789 } else if (item_spec_eq_id(CurItem->type, "Dexterity Capsule")) {
790 Me.current_dexterity_bonus = 30;
791 Me.dexterity_bonus_end_date = Me.current_game_date + 2.0 * 60;
792 } else if (item_spec_eq_id(CurItem->type, "Map Maker")) {
793 Me.map_maker_is_present = TRUE;
794 GameConfig.Automap_Visible = TRUE;
795 Play_Spell_ForceToEnergy_Sound();
796 } else if (item_spec_eq_id(CurItem->type, "Strength Pill")) {
797 Me.base_strength++;
798 } else if (item_spec_eq_id(CurItem->type, "Dexterity Pill")) {
799 Me.base_dexterity++;
800 } else if (item_spec_eq_id(CurItem->type, "Code Pill")) {
801 Me.base_cooling++;
802 } else if (item_spec_eq_id(CurItem->type, "Brain Enlargement Pill")) {
803 Me.base_cooling = 5;
804 Me.base_strength = 5;
805 Me.base_dexterity = 5;
806 Me.base_physique = 5;
807 Takeover_Game_Lost_Sound();
808 append_new_game_message(_("The doctor warned you. You are now weak and sickly."));
809 }
810
811 // Do the skill
812 if (ItemMap[CurItem->type].right_use.skill) {
813 failed_usage = !DoSkill(get_program_index_with_name(ItemMap[CurItem->type].right_use.skill), 0);
814 // Improve the skill
815 } else if (ItemMap[CurItem->type].right_use.add_skill) {
816 failed_usage = improve_program(get_program_index_with_name(ItemMap[CurItem->type].right_use.add_skill));
817
818 if(failed_usage == 0) {
819 Play_Spell_ForceToEnergy_Sound();
820 } else {
821 // TRANSLATORS: the trailing %s is a program name
822 append_new_game_message(_("You have reached the maximum skill level for %s"),
823 D_(ItemMap[CurItem->type].right_use.add_skill));
824 Takeover_Game_Deadlock_Sound();
825 }
826 }
827
828 if (!failed_usage) {
829
830 play_item_sound(CurItem->type, &Me.pos);
831
832 // Apply busy time and busy type
833 Me.busy_time = ItemMap[CurItem->type].right_use.busy_time;
834 Me.busy_type = ItemMap[CurItem->type].right_use.busy_type;
835
836 // In some cases the item concerned is a one-shot-device like a health potion, which should
837 // evaporize after the first application. Therefore we delete the item from the inventory list.
838 //
839 if (CurItem->multiplicity > 1)
840 CurItem->multiplicity--;
841 else
842 DeleteItem(CurItem);
843 }
844
845 while (MouseRightPressed())
846 SDL_Delay(1);
847 }
848
849 /**
850 * This function checks if a given coordinate within the influencers
851 * inventory grid can be considered as free or as occupied by some item.
852 */
Inv_Pos_Is_Free(int x,int y)853 int Inv_Pos_Is_Free(int x, int y)
854 {
855 int i;
856 int item_width;
857 int item_height;
858
859 for (i = 0; i < MAX_ITEMS_IN_INVENTORY - 1; i++) {
860 if (Me.Inventory[i].type == (-1))
861 continue;
862
863 if (item_held_in_hand == &Me.Inventory[i])
864 continue;
865
866 // for ( item_height = 0 ; item_height < ItemSizeTable[ Me.Inventory[ i ].type ].y ; item_height ++ )
867 for (item_height = 0; item_height < ItemMap[Me.Inventory[i].type].inv_size.y; item_height++) {
868 for (item_width = 0; item_width < ItemMap[Me.Inventory[i].type].inv_size.x; item_width++) {
869 if (((Me.Inventory[i].inventory_position.x + item_width) == x) &&
870 ((Me.Inventory[i].inventory_position.y + item_height) == y))
871 return (FALSE);
872 }
873 }
874 }
875 return TRUE;
876
877 }; // int Inv_Pos_Is_Free( Inv_Loc.x , Inv_Loc.y )
878
879 /**
880 * This function returns the index in the inventory list of the object
881 * at the inventory position x y. If no object is found to occupy that
882 * square, an index of (-1) is returned.
883 *
884 * NOTE: The mentioned coordinates refer to the squares of the inventory grid!!
885 *
886 */
GetInventoryItemAt(int x,int y)887 int GetInventoryItemAt(int x, int y)
888 {
889 int i;
890 int item_width;
891 int item_height;
892
893 for (i = 0; i < MAX_ITEMS_IN_INVENTORY - 1; i++) {
894 if (Me.Inventory[i].type == (-1))
895 continue;
896
897 for (item_height = 0; item_height < ItemMap[Me.Inventory[i].type].inv_size.y; item_height++) {
898 for (item_width = 0; item_width < ItemMap[Me.Inventory[i].type].inv_size.x; item_width++) {
899 if (((Me.Inventory[i].inventory_position.x + item_width) == x) &&
900 ((Me.Inventory[i].inventory_position.y + item_height) == y)) {
901 return (i);
902 }
903 }
904 }
905 }
906 return (-1); // Nothing found at this grabbing location!!
907
908 }; // int GetInventoryItemAt ( int x , int y )
909
910 /**
911 *
912 * Often, especially in dialogs and in order to determine if some answer
913 * should be allowed for the Tux or not, it is important to know if the
914 * Tux has some special item of a given type in inventory or not and also
915 * how many of those items the Tux really has.
916 *
917 * This function is now intended to count the number of items of a given
918 * type in the inventory of the Me.
919 *
920 */
CountItemtypeInInventory(int Itemtype)921 int CountItemtypeInInventory(int Itemtype)
922 {
923 int i;
924 int NumberOfItemsFound = 0;
925
926 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
927 if (Me.Inventory[i].type == Itemtype)
928 NumberOfItemsFound += Me.Inventory[i].multiplicity;
929 }
930 return NumberOfItemsFound;
931
932 }; // int CountItemtypeInInventory( int Itemtype )
933
934 /**
935 *
936 *
937 */
FindFirstInventoryIndexWithItemType(int Itemtype)938 static int FindFirstInventoryIndexWithItemType(int Itemtype)
939 {
940 int i;
941
942 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
943 if (Me.Inventory[i].type == Itemtype)
944 return (i);
945 }
946
947 // Severe error: Item type NOT found in inventory!!!
948 //
949 fprintf(stderr, "\n\nItemType: '%d'.\n", Itemtype);
950 error_message(__FUNCTION__, "\
951 There was an item code for an item to locate in inventory, but inventory\n\
952 did not contain this item type at all! This indicates a severe bug in FreedroidRPG.", PLEASE_INFORM | IS_FATAL);
953
954 return (-1);
955
956 }; // int FindFirstInventoryIndexWithItemType ( ItemPointer->type , PLAYER_NR_0 )
957
958 /**
959 * At some point the Tux will hand over all his items of a given type
960 * to a dialog partner. This function is intended to do exactly this:
961 * To remove all items of a given type from the inventory of a given
962 * player.
963 */
DeleteInventoryItemsOfType(int Itemtype,int amount)964 void DeleteInventoryItemsOfType(int Itemtype, int amount)
965 {
966 int i;
967 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
968 if (Me.Inventory[i].type == Itemtype) {
969 if (Me.Inventory[i].multiplicity > amount)
970 Me.Inventory[i].multiplicity -= amount;
971 else
972 DeleteItem(&(Me.Inventory[i]));
973 return;
974 }
975 }
976 }; // void DeleteInventoryItemsOfType( int Itemtype )
977
978 /**
979 * This deletes ONE item of the given type, like one bullet that has
980 * just been expended.
981 */
DeleteOneInventoryItemsOfType(int Itemtype)982 void DeleteOneInventoryItemsOfType(int Itemtype)
983 {
984 int i;
985 for (i = 0; i < MAX_ITEMS_IN_INVENTORY; i++) {
986 if (Me.Inventory[i].type == Itemtype) {
987 if (Me.Inventory[i].multiplicity > 1)
988 Me.Inventory[i].multiplicity--;
989 else
990 DeleteItem(&(Me.Inventory[i]));
991 return;
992 }
993 }
994
995 // This point must never be reached or a severe error has occurred...
996 //
997 fprintf(stderr, "\n\nItemType: '%d'.\n", Itemtype);
998 error_message(__FUNCTION__, "\
999 One single item of all the items of a given type in the Tux inventory\n\
1000 should be removed, but there was not even one such item ever found in\n\
1001 Tux inventory. Something must have gone awry...", PLEASE_INFORM | IS_FATAL);
1002
1003 }; // void DeleteOneInventoryItemsOfType( int Itemtype )
1004
MouseCursorIsInSkiERect(int x,int y)1005 static int MouseCursorIsInSkiERect(int x, int y)
1006 {
1007 if (x > 320 || x < 0)
1008 return FALSE;
1009 if (y > 480 || y < 0)
1010 return FALSE;
1011
1012 return TRUE;
1013 }
1014
1015 /**
1016 * This function checks if a given screen position lies within the inventory
1017 * rectangle or not.
1018 */
MouseCursorIsInInvRect(int x,int y)1019 int MouseCursorIsInInvRect(int x, int y)
1020 {
1021 if (!GameConfig.Inventory_Visible)
1022 return FALSE;
1023 if (x > InventoryRect.x + InventoryRect.w)
1024 return (FALSE);
1025 if (x < InventoryRect.x)
1026 return (FALSE);
1027 if (y > InventoryRect.y + InventoryRect.h)
1028 return (FALSE);
1029 if (y < InventoryRect.y)
1030 return (FALSE);
1031 return TRUE;
1032 }; // int MouseCursorIsInInvRect( int x , int y )
1033
1034 /**
1035 * This function checks if a given screen position lies within the character
1036 * rectangle or not.
1037 */
MouseCursorIsInChaRect(int x,int y)1038 int MouseCursorIsInChaRect(int x, int y)
1039 {
1040 if (!GameConfig.CharacterScreen_Visible)
1041 return FALSE;
1042 if (x > CharacterRect.x + CharacterRect.w)
1043 return (FALSE);
1044 if (x < CharacterRect.x)
1045 return (FALSE);
1046 if (y > CharacterRect.y + CharacterRect.h)
1047 return (FALSE);
1048 if (y < CharacterRect.y)
1049 return (FALSE);
1050 return TRUE;
1051 }; // int MouseCursorIsInChaRect( int x , int y )
1052
1053 /**
1054 * This function checks if a given screen position lies within the skill
1055 * rectangle or not.
1056 */
MouseCursorIsInSkiRect(int x,int y)1057 int MouseCursorIsInSkiRect(int x, int y)
1058 {
1059 if (!GameConfig.SkillScreen_Visible)
1060 return FALSE;
1061 if (x > SkillScreenRect.x + SkillScreenRect.w)
1062 return (FALSE);
1063 if (x < SkillScreenRect.x)
1064 return (FALSE);
1065 if (y > SkillScreenRect.y + SkillScreenRect.h)
1066 return (FALSE);
1067 if (y < SkillScreenRect.y)
1068 return (FALSE);
1069 return TRUE;
1070 }; // int MouseCursorIsInSkiRect( int x , int y )
1071
1072 /**
1073 * This function checks if a given screen position lies within the grid
1074 * where the inventory of the player is usually located or not.
1075 */
MouseCursorIsInInventoryGrid(int x,int y)1076 int MouseCursorIsInInventoryGrid(int x, int y)
1077 {
1078 if (!GameConfig.Inventory_Visible)
1079 return FALSE;
1080 if ((x >= INVENTORY_RECT_X) && (x <= INVENTORY_RECT_X + INVENTORY_GRID_WIDTH * INV_SUBSQUARE_WIDTH)) {
1081 if ((y >= User_Rect.y + INVENTORY_RECT_Y) &&
1082 (y <= User_Rect.y + INVENTORY_RECT_Y + INV_SUBSQUARE_HEIGHT * INVENTORY_GRID_HEIGHT)) {
1083 return TRUE;
1084 }
1085 }
1086 return (FALSE);
1087 }; // int MouseCursorIsInInventoryGrid( int x , int y )
1088
1089 /**
1090 * This function checks if a given screen position lies within the user
1091 * i.e. combat rectangle or not.
1092 */
MouseCursorIsInUserRect(int x,int y)1093 int MouseCursorIsInUserRect(int x, int y)
1094 {
1095 // no interaction with the game when the world is frozen
1096 if (world_frozen())
1097 return FALSE;
1098
1099 if (y < User_Rect.y)
1100 return (FALSE);
1101 if (y > User_Rect.y + User_Rect.h)
1102 return (FALSE);
1103
1104 if ((!GameConfig.Inventory_Visible) && (!GameConfig.CharacterScreen_Visible) && (!GameConfig.SkillScreen_Visible)) {
1105 if (x > User_Rect.x + User_Rect.w)
1106 return (FALSE);
1107 if (x < User_Rect.x)
1108 return (FALSE);
1109 return TRUE;
1110 }
1111 if ((GameConfig.Inventory_Visible && MouseCursorIsInInvRect(x, y))
1112 || (GameConfig.CharacterScreen_Visible && MouseCursorIsInChaRect(x, y)) || (GameConfig.SkillScreen_Visible
1113 && MouseCursorIsInSkiRect(x, y))
1114 || (GameConfig.skill_explanation_screen_visible && MouseCursorIsInSkiERect(x, y)))
1115 return FALSE;
1116 return TRUE;
1117 }; // int MouseCursorIsInUserRect( int x , int y )
1118
1119 /**
1120 * This function gives the x coordinate of the inventory square that
1121 * corresponds to the mouse cursor location given to the function.
1122 */
GetInventorySquare_x(int x)1123 int GetInventorySquare_x(int x)
1124 {
1125 return ((x - INVENTORY_RECT_X) / INV_SUBSQUARE_WIDTH);
1126 }; // int GetInventorySquare_x( x )
1127
1128 /**
1129 * This function gives the y coordinate of the inventory square that
1130 * corresponds to the mouse cursor location given to the function.
1131 */
GetInventorySquare_y(int y)1132 int GetInventorySquare_y(int y)
1133 {
1134 return ((y - (User_Rect.y + INVENTORY_RECT_Y)) / INV_SUBSQUARE_HEIGHT);
1135 }; // int GetInventorySquare_y( y )
1136
1137 /**
1138 * This function checks if a given item type could be dropped into the
1139 * inventory grid at location x y. Only the space is taken into account
1140 * and if other items block the way or not.
1141 */
ItemCanBeDroppedInInv(int ItemType,int InvPos_x,int InvPos_y)1142 int ItemCanBeDroppedInInv(int ItemType, int InvPos_x, int InvPos_y)
1143 {
1144 int item_height;
1145 int item_width;
1146
1147 // Perhaps the item reaches even outside the inventory grid. Then of course
1148 // it does not fit and we need/should not even test the details...
1149 //
1150 if (InvPos_x < 0 || InvPos_y < 0)
1151 return FALSE;
1152 if (ItemMap[ItemType].inv_size.x - 1 + InvPos_x >= INVENTORY_GRID_WIDTH)
1153 return (FALSE);
1154 if (ItemMap[ItemType].inv_size.y - 1 + InvPos_y >= INVENTORY_GRID_HEIGHT)
1155 return (FALSE);
1156
1157 // Now that we know, that the desired position is at least inside the inventory
1158 // grid, we can start to test for the details of the available inventory space
1159 //
1160 for (item_height = 0; item_height < ItemMap[ItemType].inv_size.y; item_height++) {
1161 for (item_width = 0; item_width < ItemMap[ItemType].inv_size.x; item_width++) {
1162 if (!Inv_Pos_Is_Free(InvPos_x + item_width, InvPos_y + item_height))
1163 return (FALSE);
1164 }
1165 }
1166 return TRUE;
1167
1168 }; // int ItemCanBeDroppedInInv ( int ItemType , int InvPos_x , int InvPos_y )
1169
1170 /**
1171 * Find a free index in the item array of the drop level.
1172 */
find_free_floor_index(level * drop_level)1173 static int find_free_floor_index(level* drop_level)
1174 {
1175 int i;
1176 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1177 if (drop_level->ItemList[i].type == -1) {
1178 return i;
1179 }
1180 }
1181
1182 // We did not find a free index.
1183 error_message(__FUNCTION__, "The item array for level %d was full.",
1184 PLEASE_INFORM, drop_level->levelnum);
1185 return -1;
1186 }
1187
1188 /**
1189 * Drop an item to the floor in the given location. No checks are done to
1190 * verify this location is unobstructed or otherwise reasonable.
1191 */
drop_item(item * item_pointer,float x,float y,int level_num)1192 item *drop_item(item *item_pointer, float x, float y, int level_num)
1193 {
1194 level *drop_level = curShip.AllLevels[level_num];
1195
1196 int index = find_free_floor_index(drop_level);
1197
1198 // Cancel the drop if we did not find an empty index to use.
1199 if (index == -1) {
1200 return NULL;
1201 }
1202
1203 // Create the item
1204 init_item(&(drop_level->ItemList[index]));
1205 MoveItem(item_pointer, &(drop_level->ItemList[index]));
1206
1207 // Place item on level
1208 drop_level->ItemList[index].inventory_position.x = -1;
1209 drop_level->ItemList[index].inventory_position.y = -1;
1210 drop_level->ItemList[index].pos.x = x;
1211 drop_level->ItemList[index].pos.y = y;
1212 drop_level->ItemList[index].pos.z = level_num;
1213 drop_level->ItemList[index].throw_time = 0.01; // something > 0
1214
1215 timeout_from_item_drop = 0.4;
1216
1217 if (item_pointer == item_held_in_hand)
1218 item_held_in_hand = NULL;
1219
1220 return &(drop_level->ItemList[index]);
1221 }
1222
1223 /**
1224 * Drop held item to the floor.
1225 *
1226 * Before calling this function, make sure item_held_in_hand != NULL
1227 */
drop_held_item(void)1228 static void drop_held_item(void)
1229 {
1230 float x = translate_pixel_to_map_location(input_axis.x, input_axis.y, TRUE);
1231 float y = translate_pixel_to_map_location(input_axis.x, input_axis.y, FALSE);
1232
1233 gps pos = { x, y, Me.pos.z };
1234 gps rpos;
1235
1236 /* The vector from Tux to the map position where the player clicked */
1237 float ax = x - Me.pos.x;
1238 float ay = y - Me.pos.y;
1239 /* The length of mentioned vector */
1240 float length = sqrt(ax * ax + ay * ay);
1241 /* The vector with the same direction but length ITEM_TAKE_DIST */
1242 float ux = ITEM_TAKE_DIST * (ax / length);
1243 float uy = ITEM_TAKE_DIST * (ay / length);
1244
1245 /* Don't let the player drop the item farther than ITEM_TAKE_DIST */
1246 if (length > ITEM_TAKE_DIST) {
1247 pos.x = Me.pos.x + ux;
1248 pos.y = Me.pos.y + uy;
1249 }
1250
1251 /* If we have an invalid drop position, attempt positions closer to Tux
1252 * until we have a good position. */
1253 colldet_filter margin = WalkableWithMarginPassFilter;
1254 margin.extra_margin = 0.2;
1255 int collision;
1256 while ((collision = !(resolve_virtual_position(&rpos, &pos) &&
1257 DirectLineColldet(Me.pos.x, Me.pos.y, pos.x, pos.y, Me.pos.z, &margin)))
1258 && sqrt(ux * ux + uy * uy) > 0.1) // length > 0.1
1259 {
1260 ux *= 0.9;
1261 uy *= 0.9;
1262 pos.x = Me.pos.x + ux;
1263 pos.y = Me.pos.y + uy;
1264 }
1265
1266 // Fall back to Tux's feet if the position is still invalid
1267 if (collision) {
1268 rpos.x = Me.pos.x;
1269 rpos.y = Me.pos.y;
1270 rpos.z = Me.pos.z;
1271 }
1272
1273 // Finally, drop the item
1274 drop_item(item_held_in_hand, rpos.x, rpos.y, rpos.z);
1275 item_held_in_hand = NULL;
1276 timeout_from_item_drop = 0.4;
1277 }
1278
1279 /**
1280 * This function checks if the usage requirements for a given item are
1281 * met by the influencer or not.
1282 */
ItemUsageRequirementsMet(item * UseItem,int MakeSound)1283 int ItemUsageRequirementsMet(item * UseItem, int MakeSound)
1284 {
1285 if (Me.strength < ItemMap[UseItem->type].item_require_strength && ItemMap[UseItem->type].item_require_strength > 0) {
1286 if (MakeSound)
1287 Not_Enough_Power_Sound();
1288 return (FALSE);
1289 }
1290 if (Me.dexterity < ItemMap[UseItem->type].item_require_dexterity && ItemMap[UseItem->type].item_require_dexterity > 0) {
1291 if (MakeSound)
1292 Not_Enough_Dist_Sound();
1293 return (FALSE);
1294 }
1295 if (Me.cooling < ItemMap[UseItem->type].item_require_cooling && ItemMap[UseItem->type].item_require_cooling > 0) {
1296 return (FALSE);
1297 }
1298 return TRUE;
1299 }; // int ItemUsageRequirementsMet( item* UseItem )
1300
1301 /**
1302 * This function checks, if the influencer mets the requirements of the
1303 * item currently held in hand by the player/influencer. Which item this
1304 * is will be found out by the function.
1305 */
HeldItemUsageRequirementsMet(void)1306 static int HeldItemUsageRequirementsMet(void)
1307 {
1308 // Check validity of HeldItem
1309 if (item_held_in_hand == NULL) {
1310 DebugPrintf(0, "\nvoid HeldItemUsageRequirementsMet ( void ) : No item in inventory seems to be currently held in hand...");
1311 return (FALSE);
1312 }
1313
1314 return (ItemUsageRequirementsMet(item_held_in_hand, TRUE));
1315 }; // int HeldItemUsageRequirementsMet( void )
1316
1317 /**
1318 * This function installs an item into a slot. The given parameter is
1319 * only the slot where this item should be installed. The source item
1320 * will be found out from inside this function. Very convenient.
1321 */
DropHeldItemToSlot(item * SlotItem)1322 static void DropHeldItemToSlot(item * SlotItem)
1323 {
1324 item *DropItemPointer; // temporary storage
1325
1326 // Chech validity of held item
1327 if (item_held_in_hand == NULL) {
1328 DebugPrintf(0, "\nvoid DropHeldItemToSlot ( void ) : No item in inventory seems to be currently held in hand...");
1329 return;
1330 }
1331
1332 // If there is an old item in the slot, we make a held item on the
1333 // floor out of it and also set the HeldItemType accordingly, so that
1334 // after the new item was placed successfully, the old item will
1335 // be out of all inventory slots, but still in the hand of the
1336 // player and ready to be put somewhere else
1337 //
1338 // But this may only be done of course, if the 'old item' is not
1339 // the item we want to put there itself!!!! HAHAHAHA!!!!
1340 //
1341 DropItemPointer = item_held_in_hand;
1342 if ((SlotItem->type != (-1)) && (item_held_in_hand != SlotItem))
1343 MakeHeldFloorItemOutOf(SlotItem);
1344 else
1345 item_held_in_hand = NULL;
1346
1347 // Move the item to the slot and mark it as no longer grabbed.
1348 MoveItem(DropItemPointer, SlotItem);
1349 play_item_sound(SlotItem->type, &Me.pos);
1350 }
1351
1352 /**
1353 * This function looks for a free inventory index. Since there are more
1354 * inventory indices than squares in the inventory grid, the function
1355 * should always be able to find a free inventory index. If not, this is
1356 * considered a severe program error, which will cause immediate
1357 * termination of FreedroidRPG.
1358 */
GetFreeInventoryIndex(void)1359 int GetFreeInventoryIndex(void)
1360 {
1361 int InvPos;
1362
1363 // We find out the first free inventory index:
1364 //
1365 for (InvPos = 0; InvPos < MAX_ITEMS_IN_INVENTORY - 1; InvPos++) {
1366 if (Me.Inventory[InvPos].type == (-1)) {
1367 return (InvPos);
1368 }
1369 }
1370
1371 // If this point is reached, the severe error mentioned above has
1372 // occurred, an error message must be printed out and the program
1373 // must be terminated.
1374 //
1375 error_message(__FUNCTION__, "\
1376 A FREE INVENTORY INDEX POSITION COULD NOT BE FOUND.\n\
1377 This is an internal error, that must never happen unless there are\n\
1378 severe bugs in the inventory system.", PLEASE_INFORM | IS_FATAL);
1379 return (-1); // just to make compilers happy.
1380 }; // int GetFreeInventoryIndex( void )
1381
1382 /**
1383 * If an item is held and then clicked again in the inventory field, this
1384 * item should be dropped into the inventory field, provided there is room
1385 * enough in it at that location. If that is the case, then the item is
1386 * dropped onto this inventory location, else nothing is done.
1387 */
DropHeldItemToInventory(void)1388 void DropHeldItemToInventory(void)
1389 {
1390 point CurPos;
1391 int FreeInvIndex;
1392 int i;
1393 FreeInvIndex = GetFreeInventoryIndex();
1394
1395 // First we check validity of held item
1396 //
1397 if (item_held_in_hand == NULL) {
1398 DebugPrintf(0, "\nvoid DropHeldItemToInventory ( void ) : No item in inventory seems to be currently held in hand...");
1399 return;
1400 }
1401
1402 // Now we want to drop the item to the right location again.
1403 // Therefore we need to find out the right position, which of course
1404 // depends as well on current mouse cursor location as well as the
1405 // size of the dropped item.
1406 //
1407 CurPos.x = GetMousePos_x() - (16 * (ItemMap[item_held_in_hand->type].inv_size.x - 1));
1408 CurPos.y = GetMousePos_y() - (16 * (ItemMap[item_held_in_hand->type].inv_size.y - 1));
1409
1410 if (ItemCanBeDroppedInInv(item_held_in_hand->type, GetInventorySquare_x(CurPos.x), GetInventorySquare_y(CurPos.y))) {
1411 CopyItem(item_held_in_hand, &(Me.Inventory[FreeInvIndex]));
1412 play_item_sound(item_held_in_hand->type, &Me.pos);
1413 Me.Inventory[FreeInvIndex].inventory_position.x = GetInventorySquare_x(CurPos.x);
1414 Me.Inventory[FreeInvIndex].inventory_position.y = GetInventorySquare_y(CurPos.y);
1415
1416 // Now that we know that the item could be dropped directly to inventory
1417 // without swapping any spaces, we can as well make the item
1418 // 'not held in hand' immediately and return
1419 //
1420 DeleteItem(item_held_in_hand);
1421 item_held_in_hand = NULL;
1422 return;
1423 } else {
1424 // So the item could not be placed into inventory directly, but maybe
1425 // it can be placed there if we swap our dropitem with some other item.
1426 // Let's test this opportunity here.
1427 //
1428 for (i = 0; i < MAX_ITEMS_IN_INVENTORY - 1; i++) {
1429 // FIRST: Security check against segfaults: It might happen that we
1430 // delete the Dropitem itself while trying several items as candidates
1431 // for removal. This would cause testing dropability with a -1 item
1432 // type and a SEGFAULT would result...
1433 //
1434 if (&(Me.Inventory[i]) == item_held_in_hand)
1435 continue;
1436
1437 // So we make a copy of each of the items we remove in order to
1438 // try to create new space for the drop item. After that, we can
1439 // remove it.
1440 //
1441 CopyItem(&(Me.Inventory[i]), &(Me.Inventory[MAX_ITEMS_IN_INVENTORY - 1]));
1442 Me.Inventory[i].type = (-1);
1443
1444 if (ItemCanBeDroppedInInv(item_held_in_hand->type, GetInventorySquare_x(CurPos.x), GetInventorySquare_y(CurPos.y))) {
1445
1446 // Copy the HelItem to the now free position
1447 CopyItem(item_held_in_hand, &(Me.Inventory[FreeInvIndex]));
1448 play_item_sound(item_held_in_hand->type, &Me.pos);
1449 Me.Inventory[FreeInvIndex].inventory_position.x = GetInventorySquare_x(CurPos.x);
1450 Me.Inventory[FreeInvIndex].inventory_position.y = GetInventorySquare_y(CurPos.y);
1451 DeleteItem(item_held_in_hand);
1452
1453 // The removed item Nr. i is put in hand in replacement of the
1454 // prior HeldItem.
1455 MakeHeldFloorItemOutOf(&(Me.Inventory[MAX_ITEMS_IN_INVENTORY - 1]));
1456
1457 return;
1458 }
1459
1460 // But if even the removal of one item was not enough, so that the new
1461 // item would fit into the inventory, then of course we should re-add the
1462 // removed item to the inventory, so that no other items get lost.
1463 //
1464 CopyItem(&(Me.Inventory[MAX_ITEMS_IN_INVENTORY - 1]), &(Me.Inventory[i]));
1465
1466 } // for: try all items if removal is the solution
1467 } // if not immediately place findable
1468 }; // void DropHeldItemToInventory( void )
1469
1470 /**
1471 *
1472 *
1473 */
get_floor_item_index_under_mouse_cursor(level ** item_lvl)1474 int get_floor_item_index_under_mouse_cursor(level **item_lvl)
1475 {
1476 gps mouse_pos;
1477 int i;
1478
1479 // no interaction with the game when the world is frozen
1480 if (world_frozen())
1481 return -1;
1482
1483 // In the case that X was pressed, we don't use the item positions but rather
1484 // we use the item slot rectangles from the item texts.
1485 //
1486 if (XPressed() || GameConfig.show_item_labels) {
1487 struct visible_level *vis_lvl, *n;
1488
1489 BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1490 level *lvl = vis_lvl->lvl_pointer;
1491
1492 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1493 if (lvl->ItemList[i].type == (-1))
1494 continue;
1495
1496 if (MouseCursorIsInRect(&(lvl->ItemList[i].text_slot_rectangle), GetMousePos_x(), GetMousePos_y())) {
1497 *item_lvl = lvl;
1498 return (i);
1499 }
1500 }
1501 }
1502 }
1503 // If no X was pressed, we only use the floor position the mouse
1504 // has pointed to and see if we can find an item that has geographically
1505 // that very same (or a similar enough) position.
1506 //
1507 else {
1508 mouse_pos.x = translate_pixel_to_map_location(input_axis.x, input_axis.y, TRUE);
1509 mouse_pos.y = translate_pixel_to_map_location(input_axis.x, input_axis.y, FALSE);
1510 mouse_pos.z = Me.pos.z;
1511
1512 gps virt_mouse_pos;
1513 struct visible_level *vis_lvl, *n;
1514
1515 BROWSE_VISIBLE_LEVELS(vis_lvl, n) {
1516
1517 level *lvl = vis_lvl->lvl_pointer;
1518 update_virtual_position(&virt_mouse_pos, &mouse_pos, lvl->levelnum);
1519
1520 for (i = 0; i < MAX_ITEMS_PER_LEVEL; i++) {
1521 if (lvl->ItemList[i].type == (-1))
1522 continue;
1523
1524 if ((fabsf(virt_mouse_pos.x - lvl->ItemList[i].pos.x) < 0.5) &&
1525 (fabsf(virt_mouse_pos.y - lvl->ItemList[i].pos.y) < 0.5)) {
1526 *item_lvl = lvl;
1527 return (i);
1528 }
1529 }
1530 }
1531 }
1532
1533 return (-1);
1534 }
1535
1536 /**
1537 * Handle inventory screen and related things: interact with items in inventory
1538 * grid, in inventory slots and in hand. Also handle dropping items in hand and
1539 * apply (right click) items.
1540 */
HandleInventoryScreen(void)1541 void HandleInventoryScreen(void)
1542 {
1543 point CurPos;
1544
1545 struct {
1546 int buttonidx;
1547 item *slot;
1548 } allslots[] = { /*list of all slots and their associated item */
1549 { WEAPON_RECT_BUTTON, &(Me.weapon_item) },
1550 { DRIVE_RECT_BUTTON, &(Me.drive_item) },
1551 { SHIELD_RECT_BUTTON, &(Me.shield_item) },
1552 { ARMOUR_RECT_BUTTON, &(Me.armour_item) },
1553 { HELMET_RECT_BUTTON, &(Me.special_item) },
1554 };
1555
1556 if (Me.energy <= 0) {
1557 return;
1558 }
1559
1560 // If the inventory is not visible there is nothing to do
1561 if (GameConfig.Inventory_Visible == FALSE) {
1562 item_held_in_hand = NULL;
1563 return;
1564 }
1565
1566 // We will need the current mouse position on several spots...
1567 CurPos.x = GetMousePos_x();
1568 CurPos.y = GetMousePos_y();
1569
1570 // Case 1: The user left-clicks while not holding an item
1571 if (MouseLeftClicked() && item_held_in_hand == NULL) {
1572
1573 // Forbid using the inventory while paralyzed
1574 if (Me.paralyze_duration) {
1575 append_new_game_message(_("You can not use the inventory while paralyzed."));
1576 return;
1577 }
1578
1579 // Case 1.1: The user left-clicks on the inventory grid
1580 if (MouseCursorIsInInventoryGrid(CurPos.x, CurPos.y)) {
1581 point Inv_GrabLoc;
1582 int Grabbed_InvPos;
1583
1584 Inv_GrabLoc.x = GetInventorySquare_x(CurPos.x);
1585 Inv_GrabLoc.y = GetInventorySquare_y(CurPos.y);
1586 Grabbed_InvPos = GetInventoryItemAt(Inv_GrabLoc.x, Inv_GrabLoc.y);
1587
1588 if (Grabbed_InvPos == (-1)) {
1589 /* No item under the cursor */
1590 return;
1591 }
1592
1593 // At this point we know, that we have just grabbed something from the inventory
1594 // So we set, that something should be displayed in the 'hand', and it should of
1595 // course be the image of the item grabbed from inventory.
1596 item_held_in_hand = &(Me.Inventory[Grabbed_InvPos]);
1597 play_item_sound(item_held_in_hand->type, &Me.pos);
1598
1599 return;
1600 }
1601
1602 // Case 1.2: The user left-clicks on one of the equipment slots
1603 unsigned int i;
1604
1605 for (i = 0; i < sizeof(allslots) / sizeof(allslots[0]); i++) {
1606 if (MouseCursorIsOnButton(allslots[i].buttonidx, CurPos.x, CurPos.y)) {
1607 if (allslots[i].slot->type > 0) {
1608 MakeHeldFloorItemOutOf(allslots[i].slot);
1609 return;
1610 }
1611 }
1612 }
1613
1614 // Case 1.3: The user left-clicks on an item on the floor
1615 int item_idx;
1616 level *item_lvl = NULL;
1617
1618 if (MouseCursorIsInUserRect(GetMousePos_x(), GetMousePos_y())
1619 && (item_idx = get_floor_item_index_under_mouse_cursor(&item_lvl)) != -1) {
1620 // Try to auto-put or auto-equip the item. If it's not possible,
1621 // the item will be 'put in hand'.
1622 if (check_for_items_to_pickup(item_lvl, item_idx)) {
1623 item *it = &item_lvl->ItemList[item_idx];
1624 if (!try_give_item(it)) {
1625 item_held_in_hand = it;
1626 }
1627 }
1628 return;
1629 }
1630
1631 // No item was picked
1632 return;
1633 }
1634
1635 // Case 2: The user left-clicks somewhere to drop a held item
1636 //
1637 if (MouseLeftClicked() && (item_held_in_hand != NULL)) {
1638
1639 // The left-click is on the inventory grid -> we must see if
1640 // the item was dropped onto a correct inventory location and
1641 // should from then on not only no longer be in the players
1642 // hand but also remain at the newly assigned position.
1643 if (MouseCursorIsInInventoryGrid(CurPos.x, CurPos.y)) {
1644 DropHeldItemToInventory();
1645 return;
1646 }
1647
1648 // The user left-clicks in the weapon's equipment slot or the bottom left corner
1649 // HUD weapon display.
1650 if (MouseCursorIsOnButton(WEAPON_RECT_BUTTON, CurPos.x, CurPos.y)
1651 || MouseCursorIsOnButton(WEAPON_MODE_BUTTON, CurPos.x, CurPos.y)) {
1652
1653 // Check if the item can be installed in the weapon slot
1654 if (ItemMap[item_held_in_hand->type].slot != WEAPON_SLOT) {
1655 append_new_game_message(_("You cannot fight with this!"));
1656 return;
1657 }
1658
1659 // Check if the user has enough skill to use the weapon
1660 if (!HeldItemUsageRequirementsMet()) {
1661 append_new_game_message(_("You cannot yet fight with this!"));
1662 return;
1663 }
1664
1665 // Now a weapon is about to be dropped to the weapons rectangle and obviously
1666 // the stat requirements for usage are met. But maybe this is a 2-handed weapon.
1667 // In this case we need to do some extra check. If it isn't a 2-handed weapon,
1668 // then we can just go ahead and equip the item
1669 if (!ItemMap[item_held_in_hand->type].weapon_needs_two_hands) {
1670 DropHeldItemToSlot(&(Me.weapon_item));
1671 return;
1672 }
1673
1674 // So, this is a 2-handed weapon. If the shield slot is just empty,
1675 // that makes matters a lot simpler, because then we can just drop
1676 // this 2-handed weapon to the weapon slot and all is fine, because
1677 //no conflicts will result...
1678 if (Me.shield_item.type == (-1)) {
1679 DropHeldItemToSlot(&(Me.weapon_item));
1680 return;
1681 }
1682
1683 // But if there is something in the shield slot too, then we need to be
1684 // a bit more sophisticated and either swap the 2-handed item in for just
1685 // the shield alone, which then will be held OR we need to refuse completely
1686 // because there might be a weapon AND a shield equipped already.
1687 if (Me.weapon_item.type == (-1)) {
1688 // first of all check requirements again but without the shield :
1689 // virtually remove the shield, compute requirements, if
1690 // everything is okay, proceed otherwise we inform the player
1691 int shield_item_type = Me.shield_item.type;
1692 Me.shield_item.type = (-1);
1693 update_all_primary_stats();
1694 if (HeldItemUsageRequirementsMet()) {
1695 DropHeldItemToSlot(&(Me.weapon_item));
1696 Me.shield_item.type = shield_item_type;
1697 MakeHeldFloorItemOutOf(&(Me.shield_item));
1698 } else {
1699 append_new_game_message(
1700 _("Two-handed weapon requirements not met: shield bonus doesn't count."));
1701 Me.shield_item.type = shield_item_type;
1702 }
1703 } else {
1704 play_sound("effects/tux_ingame_comments/ThisItemRequiresBothHands.ogg");
1705 }
1706 }
1707
1708 // The user left-clicks in the shield's equipment slot
1709 if (MouseCursorIsOnButton(SHIELD_RECT_BUTTON, CurPos.x, CurPos.y)) {
1710
1711 // Check if the item can be installed in the shield slot
1712 if (ItemMap[item_held_in_hand->type].slot != SHIELD_SLOT) {
1713 append_new_game_message(_("You cannot equip this!"));
1714 return;
1715 }
1716
1717 // Check if the user has enough skill to use the shield
1718 if (!HeldItemUsageRequirementsMet()) {
1719 append_new_game_message(_("You cannot equip this yet!"));
1720 return;
1721 }
1722
1723 // Now if there isn't any weapon equipped right now, the matter
1724 // is rather simple and we just need to do the normal drop-to-slot-thing.
1725 if (Me.weapon_item.type == (-1)) {
1726 DropHeldItemToSlot(&(Me.shield_item));
1727 return;
1728 }
1729
1730 // A shield, when equipped, will push out any 2-handed weapon currently
1731 // equipped from it's weapon slot.... So first check if a 2-handed
1732 // weapon is equipped.
1733 if (!ItemMap[Me.weapon_item.type].weapon_needs_two_hands) {
1734 DropHeldItemToSlot(&(Me.shield_item));
1735 return;
1736 }
1737
1738 // There is a 2-handed weapon equipped, so first of all check
1739 // requirements again but without the weapon :
1740 // virtually remove the weapon, compute requirements, if
1741 // everything is okay, proceed otherwise we inform the player
1742 int weapon_item_type = Me.weapon_item.type;
1743 Me.weapon_item.type = (-1);
1744 update_all_primary_stats();
1745 if (HeldItemUsageRequirementsMet()) {
1746 DropHeldItemToSlot(&(Me.shield_item));
1747 Me.weapon_item.type = weapon_item_type;
1748 MakeHeldFloorItemOutOf(&(Me.weapon_item));
1749 } else {
1750 append_new_game_message(_
1751 ("Shield requirements not met: two-handed weapon bonus doesn't count."));
1752 Me.weapon_item.type = weapon_item_type;
1753 }
1754 }
1755
1756 // The user left-clicks in another equipment slot
1757 itemspec *tocheck = &ItemMap[item_held_in_hand->type];
1758 struct {
1759 int btnidx;
1760 char propcheck;
1761 item *slot;
1762 } dropslots[] = { {
1763 DRIVE_RECT_BUTTON, (tocheck->slot == BOOT_SLOT), &(Me.drive_item)}, {
1764 ARMOUR_RECT_BUTTON, (tocheck->slot == ARMOR_SLOT), &(Me.armour_item)}, {
1765 HELMET_RECT_BUTTON, (tocheck->slot == HELM_SLOT), &(Me.special_item)},};
1766 int i;
1767 for (i = 0; i < sizeof(dropslots) / sizeof(dropslots[0]); i++) {
1768 if (MouseCursorIsOnButton(dropslots[i].btnidx, CurPos.x, CurPos.y) && dropslots[i].propcheck
1769 && HeldItemUsageRequirementsMet()) {
1770 DropHeldItemToSlot(dropslots[i].slot);
1771 return;
1772 }
1773 }
1774
1775 // The user left-clicks in the "UserRect" -> the item should
1776 // be dropped to the floor
1777 if (MouseCursorIsInUserRect(CurPos.x, CurPos.y)) {
1778 drop_held_item();
1779 return;
1780 }
1781
1782 // The left-click did not lead to anything useful
1783 return;
1784 }
1785
1786 // Case 3: The user is right-clicking inside the inventory rectangle which
1787 // would mean for us that he is applying the item under the mouse button
1788 //
1789 if (MouseRightClicked()) {
1790
1791 if (Me.readied_skill == get_program_index_with_name("Repair equipment") && Me.busy_time <= 0) {
1792 // Here we know, that the repair skill is selected, therefore we try to
1793 // repair the item currently under the mouse cursor.
1794 //
1795 if (MouseCursorIsInInventoryGrid(CurPos.x, CurPos.y)) {
1796 point Inv_GrabLoc;
1797 int Grabbed_InvPos;
1798
1799 Inv_GrabLoc.x = GetInventorySquare_x(CurPos.x);
1800 Inv_GrabLoc.y = GetInventorySquare_y(CurPos.y);
1801
1802 DebugPrintf(0, "\nTrying to repair item at inv-pos: %d %d.", Inv_GrabLoc.x, Inv_GrabLoc.y);
1803
1804 Grabbed_InvPos = GetInventoryItemAt(Inv_GrabLoc.x, Inv_GrabLoc.y);
1805 DebugPrintf(0, "\nTrying to repair inventory entry no.: %d.", Grabbed_InvPos);
1806
1807 if (Grabbed_InvPos == (-1)) {
1808 // Nothing grabbed, so we need not do anything more here..
1809 DebugPrintf(0, "\nRepairing in INVENTORY grid FAILED: NO ITEM AT THIS POSITION FOUND!");
1810 } else {
1811 if (Me.Inventory[Grabbed_InvPos].max_durability != -1)
1812 self_repair_item(&(Me.Inventory[Grabbed_InvPos]));
1813 else
1814 apply_item(&(Me.Inventory[Grabbed_InvPos]));
1815 }
1816 } else {
1817 int i;
1818 for (i = 0; i < sizeof(allslots) / sizeof(allslots[0]); i++) {
1819 if (MouseCursorIsOnButton(allslots[i].buttonidx, CurPos.x, CurPos.y)
1820 && allslots[i].slot->type != -1) {
1821 self_repair_item(allslots[i].slot);
1822 break;
1823 }
1824 }
1825 }
1826 } else {
1827 if (MouseCursorIsInInventoryGrid(CurPos.x, CurPos.y)) {
1828 point Inv_GrabLoc;
1829 int Grabbed_InvPos;
1830
1831 Inv_GrabLoc.x = GetInventorySquare_x(CurPos.x);
1832 Inv_GrabLoc.y = GetInventorySquare_y(CurPos.y);
1833
1834 Grabbed_InvPos = GetInventoryItemAt(Inv_GrabLoc.x, Inv_GrabLoc.y);
1835
1836 if ((Grabbed_InvPos != -1) && (&(Me.Inventory[Grabbed_InvPos]) != item_held_in_hand)) {
1837 // At this point we know, that we have just applied something from the inventory
1838 apply_item(&(Me.Inventory[Grabbed_InvPos]));
1839 }
1840 }
1841 }
1842 }
1843 }
1844
1845 /**
1846 *
1847 *
1848 */
raw_move_picked_up_item_to_entry(item * ItemPointer,item * TargetPointer,point Inv_Loc)1849 static void raw_move_picked_up_item_to_entry(item * ItemPointer, item * TargetPointer, point Inv_Loc)
1850 {
1851 // We add the new item to the inventory
1852 CopyItem(ItemPointer, TargetPointer);
1853 TargetPointer->inventory_position.x = Inv_Loc.x;
1854 TargetPointer->inventory_position.y = Inv_Loc.y;
1855
1856 // We make the sound of an item being taken
1857 // PlayItemSound( ItemMap[ ItemPointer->type ].sound_number );
1858 play_item_sound(ItemPointer->type, &Me.pos);
1859
1860 DeleteItem(ItemPointer);
1861 }; // void move_picked_up_item_to_entry ( ItemPointer , TargetPointer )
1862
1863 /**
1864 *
1865 */
place_item_on_this_position_if_you_can(item * ItemPointer,point Inv_Loc,int InvPos)1866 static int place_item_on_this_position_if_you_can(item * ItemPointer, point Inv_Loc, int InvPos)
1867 {
1868 int item_height;
1869 int item_width;
1870
1871 for (item_height = 0; item_height < ItemMap[ItemPointer->type].inv_size.y; item_height++) {
1872 for (item_width = 0; item_width < ItemMap[ItemPointer->type].inv_size.x; item_width++) {
1873 DebugPrintf(1, "\nAddFloorItemDirectlyToInventory: Checking pos: %d %d ", Inv_Loc.x + item_width,
1874 Inv_Loc.y + item_height);
1875 if (!Inv_Pos_Is_Free(Inv_Loc.x + item_width, Inv_Loc.y + item_height)) {
1876 Me.Inventory[InvPos].inventory_position.x = -1;
1877 Me.Inventory[InvPos].inventory_position.y = -1;
1878 // goto This_Is_No_Possible_Location;
1879 return (FALSE);
1880 }
1881 }
1882 }
1883 // if ( !Inv_Pos_Is_Free( Inv_Loc.x , Inv_Loc.y ) ) continue;
1884
1885 // At this point we know we have reached a position where we can plant this item.
1886 Me.Inventory[InvPos].inventory_position.x = Inv_Loc.x;
1887 Me.Inventory[InvPos].inventory_position.y = Inv_Loc.y;
1888 DebugPrintf(1, "\nAddFloorItemDirectlyToInventory: FINE INVENTORY POSITION FOUND!!");
1889
1890 if ((InvPos >= MAX_ITEMS_IN_INVENTORY - 1) || (Me.Inventory[InvPos].inventory_position.x == (-1))) {
1891 Me.TextVisibleTime = 0;
1892 Me.TextToBeDisplayed = _("I can't carry any more.");
1893 CantCarrySound();
1894 // can't take any more items,
1895 } else {
1896 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.Inventory[InvPos]), Inv_Loc);
1897 }
1898 return TRUE;
1899 }; // int place_item_on_this_position_if_you_can ( ... )
1900
1901 /**
1902 * This function deals with the case, that WHILE THERE IS NO INVENTORY
1903 * SCREEN OPEN, the Tux still clicks some items on the floor to pick them
1904 * up. So no big visible operation is required, but instead the items
1905 * picked up should be either auto-equipped, if possible, or they should
1906 * be put into the inventory items pool.
1907 *
1908 * \return -1 on error, 1 if the item was placed somewhere, 0 if there is no room
1909 * for the item in the inventory.
1910 */
try_give_item(item * ItemPointer)1911 int try_give_item(item *ItemPointer)
1912 {
1913 int InvPos;
1914 point Inv_Loc = { -1, -1 };
1915 int TargetItemIndex;
1916
1917 if (ItemPointer == NULL)
1918 return -1;
1919
1920 // In the special case of money, we add the amount of money to our
1921 // money counter and eliminate the item on the floor.
1922
1923 if (item_spec_eq_id(ItemPointer->type, "Valuable Circuits")) {
1924 play_item_sound(ItemPointer->type, &Me.pos);
1925 Me.Gold += ItemPointer->multiplicity;
1926 DeleteItem(ItemPointer);
1927 return 1;
1928 }
1929
1930 // In the special case, that this is an item, that groups together with others
1931 // of the same type AND we also have as item of this type already in inventory,
1932 // then we just need to manipulate multiplicity a bit and we're done. Very easy.
1933
1934 if (ItemMap[ItemPointer->type].item_group_together_in_inventory) {
1935 if (CountItemtypeInInventory(ItemPointer->type)) {
1936 TargetItemIndex = FindFirstInventoryIndexWithItemType(ItemPointer->type);
1937 Me.Inventory[TargetItemIndex].multiplicity += ItemPointer->multiplicity;
1938 play_item_sound(ItemPointer->type, &Me.pos);
1939 DeleteItem(ItemPointer);
1940 return 1;
1941 }
1942 }
1943
1944 // Maybe the item is of a kind that can be equipped right now. Then
1945 // we decide to directly drop it to the corresponding slot.
1946
1947 if ((Me.weapon_item.type == (-1)) && (ItemMap[ItemPointer->type].slot == WEAPON_SLOT)) {
1948 if (ItemUsageRequirementsMet(ItemPointer, TRUE)) {
1949 // Now we're picking up a weapon while no weapon is equipped. But still
1950 // it might be a 2-handed weapon while there is some shield equipped. Well,
1951 // when that is the case, we refuse to put it directly to the proper slot,
1952 // otherwise we do it.
1953 //
1954 if (Me.shield_item.type == (-1)) {
1955 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.weapon_item), Inv_Loc);
1956 return 1;
1957 }
1958 // So now we know that some shield item is equipped. Let's be careful: 2-handed
1959 // weapons will be rejected from direct addition to the slot.
1960 //
1961 if (!ItemMap[ItemPointer->type].weapon_needs_two_hands) {
1962 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.weapon_item), Inv_Loc);
1963 return 1;
1964 }
1965 }
1966 }
1967
1968 if ((Me.shield_item.type == (-1)) && (ItemMap[ItemPointer->type].slot == SHIELD_SLOT)) {
1969 if (ItemUsageRequirementsMet(ItemPointer, TRUE)) {
1970 // Auto-equipping shields can be done. But only if there isn't a 2-handed
1971 // weapon equipped already. Well, in case of no weapon present it's easy:
1972 //
1973 if (Me.weapon_item.type == (-1)) {
1974 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.shield_item), Inv_Loc);
1975 return 1;
1976 }
1977 // But now we know, that there is some weapon present. We need to be careful:
1978 // it might be a 2-handed weapon.
1979 //
1980 if (!ItemMap[Me.weapon_item.type].weapon_needs_two_hands) {
1981 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.shield_item), Inv_Loc);
1982 return 1;
1983 }
1984 }
1985 }
1986
1987 if ((Me.armour_item.type == (-1)) && (ItemMap[ItemPointer->type].slot == ARMOR_SLOT)) {
1988 if (ItemUsageRequirementsMet(ItemPointer, TRUE)) {
1989 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.armour_item), Inv_Loc);
1990 return 1;
1991 }
1992 }
1993
1994 if ((Me.drive_item.type == (-1)) && (ItemMap[ItemPointer->type].slot == BOOT_SLOT)) {
1995 if (ItemUsageRequirementsMet(ItemPointer, TRUE)) {
1996 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.drive_item), Inv_Loc);
1997 return 1;
1998 }
1999 }
2000
2001 if ((Me.special_item.type == (-1)) && (ItemMap[ItemPointer->type].slot == HELM_SLOT)) {
2002 if (ItemUsageRequirementsMet(ItemPointer, TRUE)) {
2003 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.special_item), Inv_Loc);
2004 return 1;
2005 }
2006 }
2007
2008 // find a free position in the inventory list
2009 for (InvPos = 0; InvPos < MAX_ITEMS_IN_INVENTORY - 1; InvPos++) {
2010 if (Me.Inventory[InvPos].type == (-1))
2011 break;
2012 }
2013
2014 // Maybe the item in question is something, that would best be placed inside
2015 // the quick inventory. If that is so, we try to put it there first. If that
2016 // isn't possible, it can still be placed somewhere outside of the quick
2017 // inventory later.
2018
2019 if ((ItemMap[ItemPointer->type].inv_size.x == 1) &&
2020 (ItemMap[ItemPointer->type].inv_size.y == 1) && (ItemMap[ItemPointer->type].right_use.tooltip)) {
2021 DebugPrintf(2, "\n\nTrying to place this item inside of the quick inventory first...");
2022 Inv_Loc.y = INVENTORY_GRID_HEIGHT - 1;
2023 for (Inv_Loc.x = 0; Inv_Loc.x < INVENTORY_GRID_WIDTH - ItemMap[ItemPointer->type].inv_size.x + 1; Inv_Loc.x++) {
2024 if (place_item_on_this_position_if_you_can(ItemPointer, Inv_Loc, InvPos))
2025 return 1;
2026 }
2027 }
2028
2029 // Find enough free squares in the inventory to fit
2030 for (Inv_Loc.y = 0; Inv_Loc.y < INVENTORY_GRID_HEIGHT - ItemMap[ItemPointer->type].inv_size.y + 1; Inv_Loc.y++) {
2031 for (Inv_Loc.x = 0; Inv_Loc.x < INVENTORY_GRID_WIDTH - ItemMap[ItemPointer->type].inv_size.x + 1; Inv_Loc.x++) {
2032 if (place_item_on_this_position_if_you_can(ItemPointer, Inv_Loc, InvPos))
2033 return 1;
2034 }
2035 }
2036
2037 // No enough free place in the inventory
2038 if (Me.Inventory[InvPos].inventory_position.x == (-1)) {
2039 return 0;
2040 }
2041
2042 // Place the item in the inventory
2043 raw_move_picked_up_item_to_entry(ItemPointer, &(Me.Inventory[InvPos]), Inv_Loc);
2044
2045 return 1;
2046 }
2047
2048 /**
2049 * \brief Places the item to the inventory of the player or to the floor.
2050 *
2051 * If the item is of an equippable type whose requirements are met and the
2052 * matching equipment slot is empty, the item is placed to the equipment slot.
2053 * Otherwise, if there's enough room in the inventory, the item is placed there.
2054 * Otherwise, the item is dropped to the floor at the feet of the player.
2055 *
2056 * \param it The item to give to the player.
2057 *
2058 * \return TRUE if equipped or placed into the inventory, FALSE if dropped on the floor
2059 */
give_item(item * it)2060 int give_item(item *it)
2061 {
2062 if (it == NULL)
2063 return FALSE;
2064
2065 if (try_give_item(it)) {
2066 return TRUE;
2067 }
2068
2069 Me.TextVisibleTime = 0;
2070 Me.TextToBeDisplayed = _("I can't carry any more.");
2071 CantCarrySound();
2072 drop_item(it, Me.pos.x, Me.pos.y, Me.pos.z);
2073
2074 return FALSE;
2075 }
2076
item_is_currently_equipped(item * Item)2077 int item_is_currently_equipped(item * Item)
2078 {
2079 if ((&(Me.weapon_item) == Item) || (&(Me.drive_item) == Item) || (&(Me.armour_item) == Item)
2080 || (&(Me.shield_item) == Item) || (&(Me.special_item) == Item))
2081 return 1;
2082
2083 return 0;
2084 }
2085
get_slot_type_by_name(char * name)2086 enum slot_type get_slot_type_by_name(char *name)
2087 {
2088 struct {
2089 const char *name;
2090 enum slot_type slot;
2091 } slots[] = { { "weapon", WEAPON_SLOT },
2092 {"drive", BOOT_SLOT },
2093 {"shield", SHIELD_SLOT },
2094 {"armour", ARMOR_SLOT },
2095 {"special", HELM_SLOT }};
2096
2097 int i;
2098 for (i = 0; i < sizeof(slots)/sizeof(slots[0]); i++) {
2099 if (!strcmp(name, slots[i].name))
2100 return slots[i].slot;
2101 }
2102 return NO_SLOT;
2103 }
2104
2105 /**
2106 * \brief Get the name of a item specs by its type.
2107 * If the item spec haven't a title, the name is used, and otherwise "UNNAMED ITEM".
2108 * \param type A valid type of itemspec (different of -1).
2109 * \return The title of the item.
2110 */
item_specs_get_name(int type)2111 const char *item_specs_get_name(int type)
2112 {
2113 if (ItemMap[type].name)
2114 return ItemMap[type].name;
2115 else if (ItemMap[type].id)
2116 return ItemMap[type].id;
2117 return "BUG - UNNAMED ITEM";
2118 }
2119
get_busy_type_by_name(char * name)2120 enum _busytype get_busy_type_by_name(char *name)
2121 {
2122 if (!strcmp(name, "drinking")) {
2123 return DRINKING_POTION;
2124 } else if (!strcmp(name, "throwing")) {
2125 return THROWING_GRENADE;
2126 } else if (!strcmp(name, "pill")) {
2127 return TAKING_PILL;
2128 }
2129 return NONE;
2130 }
2131
2132 #undef _items_c
2133