1 #include "LoadSaveData.h"
2 #include "Types.h"
3 #include "Arms_Dealer_Init.h"
4 #include "Debug.h"
5 #include "Random.h"
6 #include "Weapons.h"
7 #include "FileMan.h"
8 #include "Game_Clock.h"
9 #include "ArmsDealerInvInit.h"
10 #include "Soldier_Profile.h"
11 #include "Handle_Items.h"
12 #include "Quests.h"
13 #include "Scheduling.h"
14 #include "MemMan.h"
15 #include "Items.h"
16
17 #include "ContentManager.h"
18 #include "DealerInventory.h"
19 #include "DealerModel.h"
20 #include "GameInstance.h"
21 #include "MagazineModel.h"
22 #include "WeaponModels.h"
23
24 #include <algorithm>
25 #include <iterator>
26
27 // To reduce memory fragmentation from frequent MemRealloc(), we allocate memory for more than one special slot each
28 // time we run out of space. Odds are that if we need one, we'll need another soon.
29 #define SPECIAL_ITEMS_ALLOCED_AT_ONCE 3
30 // Once allocated, the special item slots remain allocated for the duration of the game, or until the dealer dies.
31 // This is a little bit wasteful, but saves an awful lot of hassles, and avoid unnecessary memory fragmentation
32
33 #define MIN_REPAIR_TIME_IN_MINUTES 15 // minutes
34 #define MIN_REPAIR_COST 10 // dollars
35
36 // price classes
37 #define PRICE_CLASS_JUNK 0
38 #define PRICE_CLASS_CHEAP 1
39 #define PRICE_CLASS_EXPENSIVE 2
40
41 UINT8 gubLastSpecialItemAddedAtElement = 255;
42
43
44 // THESE GET SAVED/RESTORED/RESET
45 ARMS_DEALER_STATUS gArmsDealerStatus[ NUM_ARMS_DEALERS ];
46 DEALER_ITEM_HEADER gArmsDealersInventory[ NUM_ARMS_DEALERS ][ MAXITEMS ];
47
48
49 static void AdjustCertainDealersInventory(void);
50 static void InitializeOneArmsDealer(ArmsDealerID);
51
GetDealer(UINT8 dealerID)52 const DealerModel* GetDealer(UINT8 dealerID)
53 {
54 if (dealerID >= NUM_ARMS_DEALERS)
55 {
56 ST::string err = ST::format("Invalid Dealer ID: {}", dealerID);
57 throw std::runtime_error(err.to_std_string());
58 }
59 return GCM->getDealer(dealerID);
60 }
61
InitAllArmsDealers()62 void InitAllArmsDealers()
63 {
64 //Memset all dealers' status tables to zeroes
65 std::fill(std::begin(gArmsDealerStatus), std::end(gArmsDealerStatus), ARMS_DEALER_STATUS{});
66
67 //Memset all dealers' inventory tables to zeroes
68 for (auto& inventory : gArmsDealersInventory)
69 {
70 std::fill(std::begin(inventory), std::end(inventory), DEALER_ITEM_HEADER{});
71 }
72
73 //Initialize the initial status & inventory for each of the arms dealers
74 for (ArmsDealerID ubArmsDealer = ARMS_DEALER_FIRST; ubArmsDealer < NUM_ARMS_DEALERS; ++ubArmsDealer)
75 {
76 InitializeOneArmsDealer( ubArmsDealer );
77 }
78
79 //make sure certain items are in stock and certain limits are respected
80 AdjustCertainDealersInventory( );
81 }
82
83
84 static void ArmsDealerGetsFreshStock(ArmsDealerID, UINT16 usItemIndex, UINT8 ubNumItems);
85
86
InitializeOneArmsDealer(ArmsDealerID const ubArmsDealer)87 static void InitializeOneArmsDealer(ArmsDealerID const ubArmsDealer)
88 {
89 UINT16 usItemIndex;
90 UINT8 ubNumItems=0;
91
92
93 gArmsDealerStatus[ ubArmsDealer ] = ARMS_DEALER_STATUS{};
94 std::fill_n(gArmsDealersInventory[ ubArmsDealer ], static_cast<size_t>(MAXITEMS), DEALER_ITEM_HEADER{});
95
96
97 //Reset the arms dealers cash on hand to the default initial value
98 gArmsDealerStatus[ ubArmsDealer ].uiArmsDealersCash = GetDealer(ubArmsDealer)->initialCash;
99
100 //if the arms dealer isn't supposed to have any items (includes all repairmen)
101 if( GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::HAS_NO_INVENTORY) )
102 {
103 return;
104 }
105
106
107 //loop through all the item types
108 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
109 {
110 //Can the item be sold by the arms dealer
111 if( CanDealerTransactItem( ubArmsDealer, usItemIndex, FALSE ) )
112 {
113 //Setup an initial amount for the items (treat items as new, how many are used isn't known yet)
114 ubNumItems = DetermineInitialInvItems( ubArmsDealer, usItemIndex, GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ), FALSE );
115
116 //if there are any initial items
117 if( ubNumItems > 0 )
118 {
119 ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, ubNumItems );
120 }
121 }
122 }
123 }
124
125
126 static void FreeSpecialItemArray(DEALER_ITEM_HEADER* pDealerItem);
127
128
ShutDownArmsDealers()129 void ShutDownArmsDealers()
130 {
131 UINT16 usItemIndex;
132
133 // loop through all the dealers
134 for (ArmsDealerID ubArmsDealer = ARMS_DEALER_FIRST; ubArmsDealer < NUM_ARMS_DEALERS; ++ubArmsDealer)
135 {
136
137 //loop through all the item types
138 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
139 {
140 if (gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() > 0)
141 {
142 FreeSpecialItemArray( &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ] );
143 }
144 }
145 }
146 }
147
148
SaveArmsDealerInventoryToSaveGameFile(HWFILE const f)149 void SaveArmsDealerInventoryToSaveGameFile(HWFILE const f)
150 {
151 FileWrite(f, gArmsDealerStatus, sizeof(gArmsDealerStatus));
152
153 for (DEALER_ITEM_HEADER const (*dealer)[MAXITEMS] = gArmsDealersInventory; dealer != endof(gArmsDealersInventory); ++dealer)
154 {
155 FOR_EACH(DEALER_ITEM_HEADER const, item, *dealer)
156 {
157 BYTE data[16];
158 DataWriter d{data};
159 INJ_U8( d, item->ubTotalItems)
160 INJ_U8( d, item->ubPerfectItems)
161 INJ_U8( d, item->ubStrayAmmo)
162 Assert(item->SpecialItem.size() <= UINT8_MAX);
163 INJ_U8( d, static_cast<UINT8>(item->SpecialItem.size()))
164 INJ_SKIP(d, 4)
165 INJ_U32( d, item->uiOrderArrivalTime)
166 INJ_U8( d, item->ubQtyOnOrder)
167 INJ_BOOL(d, item->fPreviouslyEligible)
168 INJ_SKIP(d, 2)
169 Assert(d.getConsumed() == lengthof(data));
170
171 FileWrite(f, data, sizeof(data));
172 }
173 }
174
175 // Save special items
176 for (ArmsDealerID dealer = ARMS_DEALER_FIRST; dealer != NUM_ARMS_DEALERS; ++dealer)
177 {
178 for (UINT16 item_idx = 1; item_idx != MAXITEMS; ++item_idx)
179 {
180 DEALER_ITEM_HEADER const& di = gArmsDealersInventory[dealer][item_idx];
181 if (di.SpecialItem.size() == 0) continue;
182 FileWrite(f, di.SpecialItem.data(), sizeof(DEALER_SPECIAL_ITEM) * di.SpecialItem.size());
183 }
184 }
185 }
186
187
188 static void AllocMemsetSpecialItemArray(DEALER_ITEM_HEADER*, UINT8 ubElementsNeeded);
189
190
LoadArmsDealerInventoryFromSavedGameFile(HWFILE const f,UINT32 const savegame_version)191 void LoadArmsDealerInventoryFromSavedGameFile(HWFILE const f, UINT32 const savegame_version)
192 {
193 // Free all the dealers special inventory arrays
194 ShutDownArmsDealers();
195
196 // Elgin was added to the dealers list in Game Version #54, enlarging these 2 tables
197 // Manny was added to the dealers list in Game Version #55, enlarging these 2 tables
198 UINT32 const n_dealers_saved =
199 savegame_version < 54 ? NUM_ARMS_DEALERS - 2 : // without Elgin and Manny
200 savegame_version < 55 ? NUM_ARMS_DEALERS - 1 : // without Manny
201 NUM_ARMS_DEALERS;
202
203 FileRead(f, gArmsDealerStatus, n_dealers_saved * sizeof(*gArmsDealerStatus));
204
205 for (DEALER_ITEM_HEADER (*dealer)[MAXITEMS] = gArmsDealersInventory; dealer != gArmsDealersInventory + n_dealers_saved; ++dealer)
206 {
207 FOR_EACH(DEALER_ITEM_HEADER, item, *dealer)
208 {
209 BYTE data[16];
210 FileRead(f, data, sizeof(data));
211
212 DataReader d{data};
213 EXTR_U8( d, item->ubTotalItems)
214 EXTR_U8( d, item->ubPerfectItems)
215 EXTR_U8( d, item->ubStrayAmmo)
216 UINT8 numSpecialItem = 0;
217 EXTR_U8( d, numSpecialItem)
218 EXTR_SKIP(d, 4)
219 EXTR_U32( d, item->uiOrderArrivalTime)
220 EXTR_U8( d, item->ubQtyOnOrder)
221 EXTR_BOOL(d, item->fPreviouslyEligible)
222 EXTR_SKIP(d, 2)
223 Assert(d.getConsumed() == lengthof(data));
224 AllocMemsetSpecialItemArray(item, numSpecialItem);
225 }
226 }
227
228 if (savegame_version < 54) InitializeOneArmsDealer(ARMS_DEALER_ELGIN);
229 if (savegame_version < 55) InitializeOneArmsDealer(ARMS_DEALER_MANNY);
230
231 // Load special items
232 for (ArmsDealerID dealer = ARMS_DEALER_FIRST; dealer != NUM_ARMS_DEALERS; ++dealer)
233 {
234 for (UINT16 item_idx = 1; item_idx != MAXITEMS; ++item_idx)
235 {
236 DEALER_ITEM_HEADER& di = gArmsDealersInventory[dealer][item_idx];
237 if (di.SpecialItem.size() == 0) continue;
238 FileRead(f, di.SpecialItem.data(), sizeof(DEALER_SPECIAL_ITEM) * di.SpecialItem.size());
239 }
240 }
241 }
242
243
244 static void ConvertCreatureBloodToElixir(void);
245 static void DailyCheckOnItemQuantities(void);
246 static void SimulateArmsDealerCustomer(void);
247
248
DailyUpdateOfArmsDealersInventory()249 void DailyUpdateOfArmsDealersInventory()
250 {
251 // if Gabby has creature blood, start turning it into extra elixir
252 ConvertCreatureBloodToElixir();
253
254 //Simulate other customers buying inventory from the dealer
255 SimulateArmsDealerCustomer();
256
257 //if there are some items that are out of stock, order some more
258 DailyCheckOnItemQuantities();
259
260 //make sure certain items are in stock and certain limits are respected
261 AdjustCertainDealersInventory( );
262 }
263
264
265 // Once a day, loop through each dealer's inventory items and possibly sell some
SimulateArmsDealerCustomer(void)266 static void SimulateArmsDealerCustomer(void)
267 {
268 UINT16 usItemIndex;
269 UINT8 ubItemsSold=0;
270 UINT8 ubElement;
271 SPECIAL_ITEM_INFO SpclItemInfo;
272
273 //loop through all the arms dealers
274 for (ArmsDealerID ubArmsDealer = ARMS_DEALER_FIRST; ubArmsDealer < NUM_ARMS_DEALERS; ++ubArmsDealer)
275 {
276 if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
277 continue;
278
279 //if the arms dealer isn't supposed to have any items (includes all repairmen)
280 if( GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::HAS_NO_INVENTORY ))
281 continue;
282
283 //loop through all items of the same type
284 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
285 {
286 //if there are some of these in stock
287 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0)
288 {
289 // first, try to sell all the new (perfect) ones
290 if ( usItemIndex == JAR_ELIXIR )
291 {
292 // only allow selling of standard # of items so those converted from blood given by player will be available
293 ubItemsSold = HowManyItemsAreSold( ubArmsDealer, usItemIndex, (UINT8) __min( 3, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems), FALSE);
294 }
295 else
296 {
297 ubItemsSold = HowManyItemsAreSold( ubArmsDealer, usItemIndex, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems, FALSE);
298 }
299 if ( ubItemsSold > 0)
300 {
301 // create item info describing a perfect item
302 SetSpecialItemInfoToDefaults( &SpclItemInfo );
303 //Now remove that many NEW ones (condition 100) of that item
304 RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ubItemsSold);
305 }
306
307 // next, try to sell all the used ones, gotta do these one at a time so we can remove them by element
308 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
309 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
310 {
311 // don't worry about negative condition, repairmen can't come this far, they don't sell!
312 if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive )
313 {
314 // try selling just this one
315 if (HowManyItemsAreSold( ubArmsDealer, usItemIndex, 1, TRUE) > 0)
316 {
317 //Sold, now remove that particular USED one!
318 RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement );
319 }
320 }
321 }
322 }
323 }
324 }
325 }
326
327
DailyCheckOnItemQuantities(void)328 static void DailyCheckOnItemQuantities(void)
329 {
330 UINT16 usItemIndex;
331 UINT8 ubMaxSupply;
332 UINT8 ubNumItems;
333 UINT32 uiArrivalDay;
334 BOOLEAN fPrevElig;
335 UINT8 ubReorderDays;
336
337 //loop through all the arms dealers
338 for (ArmsDealerID ubArmsDealer = ARMS_DEALER_FIRST; ubArmsDealer < NUM_ARMS_DEALERS; ++ubArmsDealer)
339 {
340 if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
341 continue;
342
343 //Reset the arms dealers cash on hand to the default initial value
344 gArmsDealerStatus[ ubArmsDealer ].uiArmsDealersCash = GetDealer(ubArmsDealer)->initialCash;
345
346 //if the arms dealer isn't supposed to have any items (includes all repairmen)
347 if( GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::HAS_NO_INVENTORY ))
348 continue;
349
350
351 //loop through all items of the same type
352 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
353 {
354 //if the dealer can sell the item type
355 if( CanDealerTransactItem( ubArmsDealer, usItemIndex, FALSE ) )
356 {
357 //if there are no items on order
358 if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder == 0 )
359 {
360 ubMaxSupply = GetDealersMaxItemAmount( ubArmsDealer, usItemIndex );
361
362 //if the qty on hand is half the desired amount or fewer
363 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems <= (UINT32)( ubMaxSupply / 2 ) )
364 {
365 // remember value of the "previously eligible" flag
366 fPrevElig = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].fPreviouslyEligible;
367
368 //determine if the item can be restocked (assume new, use items aren't checked for until the stuff arrives)
369 if (ItemTransactionOccurs( ubArmsDealer, usItemIndex, DEALER_BUYING, FALSE ))
370 {
371 // figure out how many items to reorder (items are reordered an entire batch at a time)
372 ubNumItems = HowManyItemsToReorder( ubMaxSupply, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
373
374 // if this is the first day the player is eligible to have access to this thing
375 if ( !fPrevElig )
376 {
377 // eliminate the ordering delay and stock the items instantly!
378 // This is just a way to reward the player right away for making
379 // progress without the reordering lag...
380 ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, ubNumItems );
381 }
382 else
383 {
384 if ( ( ubArmsDealer == ARMS_DEALER_TONY ) || ( ubArmsDealer == ARMS_DEALER_DEVIN ) )
385 {
386 // the stuff Tony and Devin sell is imported, so it takes longer
387 // to arrive (for game balance)
388 ubReorderDays = ( UINT8) ( 2 + Random( 2 ) ); // 2-3 days
389 }
390 else
391 {
392 ubReorderDays = ( UINT8) ( 1 + Random( 2 ) ); // 1-2 days
393 }
394
395 //Determine when the inventory should arrive
396 uiArrivalDay = GetWorldDay() + ubReorderDays; // consider changing this to minutes
397
398 // post new order
399 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder = ubNumItems;
400 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].uiOrderArrivalTime = uiArrivalDay;
401 }
402 }
403 }
404 }
405 else //items are on order
406 {
407 //and today is the day the items come in
408 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].uiOrderArrivalTime >= GetWorldDay() )
409 {
410 ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder);
411
412 //reset order
413 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder = 0;
414 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].uiOrderArrivalTime = 0;
415 }
416 }
417 }
418 }
419 }
420 }
421
422
ConvertCreatureBloodToElixir(void)423 static void ConvertCreatureBloodToElixir(void)
424 {
425 UINT8 ubBloodAvailable;
426 UINT8 ubAmountToConvert;
427 SPECIAL_ITEM_INFO SpclItemInfo;
428
429 ubBloodAvailable = gArmsDealersInventory[ ARMS_DEALER_GABBY ][ JAR_CREATURE_BLOOD ].ubTotalItems;
430 if ( ubBloodAvailable )
431 {
432 // start converting blood into elixir!
433 //ubAmountToConvert = (UINT8) __min( 5 + Random( 3 ), ubBloodAvailable );
434 ubAmountToConvert = ubBloodAvailable;
435
436 // create item info describing a perfect item
437 SetSpecialItemInfoToDefaults( &SpclItemInfo );
438
439 //Now remove that many NEW ones (condition 100) of that item
440 RemoveItemFromArmsDealerInventory( ARMS_DEALER_GABBY, JAR_CREATURE_BLOOD, &SpclItemInfo, ubAmountToConvert );
441
442 ArmsDealerGetsFreshStock( ARMS_DEALER_GABBY, JAR_ELIXIR, ubAmountToConvert );
443 }
444 }
445
446
447 static void AddItemToArmsDealerInventory(ArmsDealerID, UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, UINT8 ubHowMany);
448 static void GuaranteeAtLeastOneItemOfType(ArmsDealerID, UINT32 uiDealerItemType);
449 static void GuaranteeMinimumAlcohol(ArmsDealerID);
450 static void LimitArmsDealersInventory(ArmsDealerID, UINT32 uiDealerItemType, UINT8 ubMaxNumberOfItemType);
451
452
AdjustCertainDealersInventory(void)453 static void AdjustCertainDealersInventory(void)
454 {
455 auto dealers = GCM->getDealers();
456
457 //Adjust Tony's items (this restocks *instantly* 1/day, doesn't use the reorder system)
458 GuaranteeAtLeastOneItemOfType( ARMS_DEALER_TONY, ARMS_DEALER_BIG_GUNS );
459 LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_BIG_GUNS, 2 );
460 LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_HANDGUNCLASS, 3 );
461 LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_AMMO, 8 );
462
463 //Adjust all bartenders' alcohol levels to a minimum
464 for (auto dealer : dealers)
465 {
466 if (dealer->hasFlag(ArmsDealerFlag::SELLS_ALCOHOL))
467 {
468 GuaranteeMinimumAlcohol((ArmsDealerID)dealer->dealerID);
469 }
470 }
471
472 //make sure Sam (hardware guy) has at least one empty jar
473 GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_SAM, JAR, 1 );
474
475 if ( CheckFact( FACT_ESTONI_REFUELLING_POSSIBLE, 0 ) )
476 {
477 // gas is restocked regularly, unlike most items
478 for (auto dealer : dealers)
479 {
480 if (dealer->hasFlag(ArmsDealerFlag::SELLS_FUEL))
481 {
482 GuaranteeAtLeastXItemsOfIndex((ArmsDealerID)dealer->dealerID, GAS_CAN, (UINT8)(4 + Random(3)));
483 }
484 }
485 }
486
487 //If the player hasn't bought a video camera from Franz yet, make sure Franz has one to sell
488 if( !( gArmsDealerStatus[ ARMS_DEALER_FRANZ ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__FRANZ_HAS_SOLD_VIDEO_CAMERA_TO_PLAYER ) )
489 {
490 GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_FRANZ, VIDEO_CAMERA, 1 );
491 }
492 }
493
494
495 static UINT32 GetArmsDealerItemTypeFromItemNumber(UINT16 usItem);
496 static void RemoveRandomItemFromArmsDealerInventory(ArmsDealerID, UINT16 usItemIndex, UINT8 ubHowMany);
497
498
LimitArmsDealersInventory(ArmsDealerID const ubArmsDealer,UINT32 uiDealerItemType,UINT8 ubMaxNumberOfItemType)499 static void LimitArmsDealersInventory(ArmsDealerID const ubArmsDealer, UINT32 uiDealerItemType, UINT8 ubMaxNumberOfItemType)
500 {
501 UINT16 usItemIndex=0;
502 UINT32 uiItemsToRemove=0;
503 SPECIAL_ITEM_INFO SpclItemInfo;
504
505 UINT16 usAvailableItem[ MAXITEMS ] = { NOTHING };
506 UINT8 ubNumberOfAvailableItem[ MAXITEMS ] = { 0 };
507 UINT32 uiTotalNumberOfItems = 0, uiRandomChoice;
508 UINT32 uiNumAvailableItems = 0, uiIndex;
509
510 // not permitted for repair dealers - would take extra code to avoid counting items under repair!
511 Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
512
513 if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
514 return;
515
516 //loop through all items of the same class and count the number in stock
517 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
518 {
519 //if there is some items in stock
520 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0)
521 {
522 //if the item is of the same dealer item type
523 if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) )
524 {
525 usAvailableItem[ uiNumAvailableItems ] = usItemIndex;
526
527 //if the dealer item type is ammo
528 if( uiDealerItemType == ARMS_DEALER_AMMO )
529 {
530 // all ammo of same type counts as only one item
531 ubNumberOfAvailableItem[ uiNumAvailableItems ] = 1;
532 uiTotalNumberOfItems++;
533 }
534 else
535 {
536 // items being repaired don't count against the limit
537 ubNumberOfAvailableItem[ uiNumAvailableItems ] = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems;
538 uiTotalNumberOfItems += ubNumberOfAvailableItem[ uiNumAvailableItems ];
539 }
540 uiNumAvailableItems++;
541 }
542 }
543 }
544
545 //if there is more of the given type than we want
546 if( uiNumAvailableItems > ubMaxNumberOfItemType )
547 {
548 uiItemsToRemove = uiNumAvailableItems - ubMaxNumberOfItemType;
549
550 do
551 {
552 uiRandomChoice = Random( uiTotalNumberOfItems );
553
554 for ( uiIndex = 0; uiIndex < uiNumAvailableItems; uiIndex++ )
555 {
556 if ( uiRandomChoice <= ubNumberOfAvailableItem[ uiIndex ] )
557 {
558 usItemIndex = usAvailableItem[ uiIndex ];
559 if ( uiDealerItemType == ARMS_DEALER_AMMO )
560 {
561 // remove all of them, since each ammo item counts as only one "item" here
562 // create item info describing a perfect item
563 SetSpecialItemInfoToDefaults( &SpclItemInfo );
564 // ammo will always be only condition 100, there's never any in special slots
565 RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
566 }
567 else
568 {
569 // pick 1 random one, don't care about its condition
570 RemoveRandomItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, 1 );
571 }
572 // now remove entry from the array by replacing it with the last and decrementing
573 // the size of the array
574 usAvailableItem[ uiIndex ] = usAvailableItem[ uiNumAvailableItems - 1 ];
575 ubNumberOfAvailableItem[ uiIndex ] = ubNumberOfAvailableItem[ uiNumAvailableItems - 1 ];
576 uiNumAvailableItems--;
577
578 // decrement count of # of items to remove
579 uiItemsToRemove--;
580 break; // and out of 'for' loop
581
582 }
583 else
584 {
585 // next item!
586 uiRandomChoice -= ubNumberOfAvailableItem[ uiIndex ];
587 }
588 }
589
590 /*
591 //loop through all items of the same type
592 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
593 {
594 //if there are some non-repairing items in stock
595 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems )
596 {
597 //if the item is of the same dealer item type
598 if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) )
599 {
600 // a random chance that the item will be removed
601 if( Random( 100 ) < 30 )
602 {
603 //remove the item
604
605 //if the dealer item type is ammo
606 if( uiDealerItemType == ARMS_DEALER_AMMO )
607 {
608 // remove all of them, since each ammo item counts as only one "item" here
609
610 // create item info describing a perfect item
611 SetSpecialItemInfoToDefaults( &SpclItemInfo );
612 // ammo will always be only condition 100, there's never any in special slots
613 RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
614 }
615 else
616 {
617 // pick 1 random one, don't care about its condition
618 RemoveRandomItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, 1 );
619 }
620
621 uiItemsToRemove--;
622 if( uiItemsToRemove == 0)
623 break;
624 }
625 }
626 }
627 }*/
628 } while (uiItemsToRemove > 0);
629 }
630 }
631
632
GuaranteeAtLeastOneItemOfType(ArmsDealerID const ubArmsDealer,UINT32 uiDealerItemType)633 static void GuaranteeAtLeastOneItemOfType(ArmsDealerID const ubArmsDealer, UINT32 uiDealerItemType)
634 {
635 UINT16 usItemIndex;
636 UINT8 ubChance;
637 UINT16 usAvailableItem[ MAXITEMS ] = { NOTHING };
638 UINT8 ubChanceForAvailableItem[ MAXITEMS ] = { 0 };
639 UINT32 uiTotalChances = 0;
640 UINT32 uiNumAvailableItems = 0, uiIndex, uiRandomChoice;
641
642 // not permitted for repair dealers - would take extra code to avoid counting items under repair!
643 Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
644
645 if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
646 return;
647
648 //loop through all items of the same type
649 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
650 {
651 //if the item is of the same dealer item type
652 if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) )
653 {
654 //if there are any of these in stock
655 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 )
656 {
657 //there is already at least 1 item of that type, return
658 return;
659 }
660
661 // if he can stock it (it appears in his inventory list)
662 if( GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ) > 0)
663 {
664 // and the stage of the game gives him a chance to have it (assume new)
665 ubChance = ChanceOfItemTransaction( ubArmsDealer, usItemIndex, DEALER_BUYING, FALSE );
666 if ( ubChance > 0 )
667 {
668 usAvailableItem[ uiNumAvailableItems ] = usItemIndex;
669 ubChanceForAvailableItem[ uiNumAvailableItems ] = ubChance;
670 uiNumAvailableItems++;
671 uiTotalChances += ubChance;
672 }
673 }
674 }
675 }
676
677 // if there aren't any such items, the following loop would never finish, so quit before trying it!
678 if (uiNumAvailableItems == 0)
679 {
680 return;
681 }
682
683
684 // CJC: randomly pick one of available items by weighted random selection.
685
686 // randomize number within uiTotalChances and then loop forwards till we find that item
687 uiRandomChoice = Random( uiTotalChances );
688
689 for ( uiIndex = 0; uiIndex < uiNumAvailableItems; uiIndex++ )
690 {
691 if ( uiRandomChoice <= ubChanceForAvailableItem[ uiIndex ] )
692 {
693 ArmsDealerGetsFreshStock( ubArmsDealer, usAvailableItem[ uiIndex ], 1 );
694 return;
695 }
696 else
697 {
698 // next item!
699 uiRandomChoice -= ubChanceForAvailableItem[ uiIndex ];
700 }
701 }
702
703 // internal logic failure!
704 }
705
706
GuaranteeAtLeastXItemsOfIndex(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 const ubHowMany)707 void GuaranteeAtLeastXItemsOfIndex(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 const ubHowMany)
708 {
709 // not permitted for repair dealers - would take extra code to avoid counting items under repair!
710 Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
711
712 if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
713 return;
714
715 //if there are any of these in stock
716 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems >= ubHowMany )
717 {
718 // have what we need...
719 return;
720 }
721
722 // if he can stock it (it appears in his inventory list)
723 // RESTRICTION REMOVED: Jake must be able to guarantee GAS even though it's not in his list, it's presence is conditional
724 //if( GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ) > 0)
725 {
726 //add the item
727 ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, (UINT8)( ubHowMany - gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ) );
728 }
729 }
730
731
GetArmsDealerItemTypeFromItemNumber(UINT16 usItem)732 static UINT32 GetArmsDealerItemTypeFromItemNumber(UINT16 usItem)
733 {
734 switch( GCM->getItem(usItem)->getItemClass() )
735 {
736 case IC_NONE: return 0;
737
738 case IC_GUN:
739 switch( GCM->getItem(usItem)->asWeapon()->ubWeaponClass )
740 {
741 case HANDGUNCLASS:
742 return ARMS_DEALER_HANDGUNCLASS;
743
744 case RIFLECLASS:
745 return ItemIsARocketRifle(usItem) ?
746 ARMS_DEALER_ROCKET_RIFLE : ARMS_DEALER_RIFLECLASS;
747
748 case SHOTGUNCLASS:
749 return ARMS_DEALER_SHOTGUNCLASS;
750 case SMGCLASS:
751 return ARMS_DEALER_SMGCLASS;
752 case MGCLASS:
753 return ARMS_DEALER_MGCLASS;
754 case MONSTERCLASS:
755 return 0;
756 case KNIFECLASS:
757 return ARMS_DEALER_KNIFECLASS;
758 }
759 break;
760
761 case IC_PUNCH:
762 if (usItem == NOTHING)
763 return 0;
764 // else treat as blade
765 // fallthrough
766 case IC_BLADE:
767 case IC_THROWING_KNIFE:
768 return ARMS_DEALER_BLADE;
769
770 case IC_LAUNCHER:
771 return ARMS_DEALER_LAUNCHER;
772 case IC_ARMOUR:
773 return ARMS_DEALER_ARMOUR;
774 case IC_MEDKIT:
775 return ARMS_DEALER_MEDKIT;
776 case IC_KIT:
777 return ARMS_DEALER_KIT;
778
779 case IC_MISC:
780 //switch on the type of item
781 switch( usItem )
782 {
783 case BEER:
784 case WINE:
785 case ALCOHOL:
786 return( ARMS_DEALER_ALCOHOL );
787
788 case METALDETECTOR:
789 case LASERSCOPE:
790 //case REMDETONATOR:
791 return( ARMS_DEALER_ELECTRONICS );
792
793 case CANTEEN:
794 case CROWBAR:
795 case WIRECUTTERS:
796 return( ARMS_DEALER_HARDWARE );
797
798 case ADRENALINE_BOOSTER:
799 case REGEN_BOOSTER:
800 case SYRINGE_3:
801 case SYRINGE_4:
802 case SYRINGE_5:
803 return( ARMS_DEALER_MEDICAL );
804
805 case SILENCER:
806 case SNIPERSCOPE:
807 case BIPOD:
808 case DUCKBILL:
809 return( ARMS_DEALER_ATTACHMENTS );
810
811 case DETONATOR:
812 case REMDETONATOR:
813 case REMOTEBOMBTRIGGER:
814 return( ARMS_DEALER_DETONATORS );
815
816 default: return ARMS_DEALER_MISC;
817 }
818
819 case IC_AMMO:
820 return ARMS_DEALER_AMMO;
821
822 case IC_FACE:
823 switch( usItem )
824 {
825 case EXTENDEDEAR:
826 case NIGHTGOGGLES:
827 case ROBOT_REMOTE_CONTROL:
828 return( ARMS_DEALER_ELECTRONICS );
829
830 default:
831 return ARMS_DEALER_FACE;
832 }
833
834 case IC_THROWN:
835 return 0; // return ARMS_DEALER_THROWN;
836 case IC_KEY:
837 return 0; // return ARMS_DEALER_KEY;
838 case IC_GRENADE:
839 return ARMS_DEALER_GRENADE;
840 case IC_BOMB:
841 return ARMS_DEALER_BOMB;
842 case IC_EXPLOSV:
843 return ARMS_DEALER_EXPLOSV;
844
845 case IC_TENTACLES:
846 case IC_MONEY:
847 return( 0 );
848
849 default:
850 AssertMsg(FALSE, String("GetArmsDealerItemTypeFromItemNumber(), invalid class %d for item %d. DF 0.",
851 GCM->getItem(usItem)->getItemClass(), usItem));
852 break;
853 }
854 return( 0 );
855 }
856
857
858
IsMercADealer(UINT8 ubMercID)859 BOOLEAN IsMercADealer( UINT8 ubMercID )
860 {
861 UINT8 cnt;
862
863 // Manny is not actually a valid dealer unless a particular event sets that fact
864 if( ( ubMercID == MANNY ) && !CheckFact( FACT_MANNY_IS_BARTENDER, 0 ) )
865 {
866 return( FALSE );
867 }
868
869 //loop through the list of arms dealers
870 for( cnt=0; cnt<NUM_ARMS_DEALERS; cnt++ )
871 {
872 if( GetDealer(cnt)->profileID == ubMercID )
873 return( TRUE );
874 }
875 return( FALSE );
876 }
877
878
GetArmsDealerIDFromMercID(UINT8 const ubMercID)879 ArmsDealerID GetArmsDealerIDFromMercID(UINT8 const ubMercID)
880 {
881 //loop through the list of arms dealers
882 for (ArmsDealerID cnt = ARMS_DEALER_FIRST; cnt < NUM_ARMS_DEALERS; ++cnt)
883 {
884 if( GetDealer(cnt)->profileID == ubMercID )
885 return( cnt );
886 }
887
888 return ARMS_DEALER_INVALID;
889 }
890
891
892
GetTypeOfArmsDealer(UINT8 ubDealerID)893 ArmsDealerType GetTypeOfArmsDealer(UINT8 ubDealerID)
894 {
895 if (ubDealerID >= NUM_ARMS_DEALERS)
896 {
897 return ArmsDealerType::NOT_VALID_DEALER;
898 }
899 return GetDealer(ubDealerID)->type;
900 }
901
902
DoesDealerDoRepairs(ArmsDealerID const ubArmsDealer)903 BOOLEAN DoesDealerDoRepairs(ArmsDealerID const ubArmsDealer)
904 {
905 return GetTypeOfArmsDealer(ubArmsDealer) == ARMS_DEALER_REPAIRS;
906 }
907
908
RepairmanIsFixingItemsButNoneAreDoneYet(UINT8 ubProfileID)909 BOOLEAN RepairmanIsFixingItemsButNoneAreDoneYet( UINT8 ubProfileID )
910 {
911 BOOLEAN fHaveOnlyUnRepairedItems=FALSE;
912 UINT8 ubElement;
913 UINT16 usItemIndex;
914
915 ArmsDealerID const bArmsDealer = GetArmsDealerIDFromMercID( ubProfileID );
916 if (bArmsDealer == ARMS_DEALER_INVALID) return FALSE;
917
918 //if the dealer is not a repair dealer, return
919 if( !DoesDealerDoRepairs( bArmsDealer ) )
920 return( FALSE );
921
922 //loop through the dealers inventory and check if there are only unrepaired items
923 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
924 {
925 //if there is some items in stock
926 if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubTotalItems )
927 {
928 //loop through the array of items
929 Assert(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
930 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
931 {
932 if ( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive )
933 {
934 //if the items status is below 0, the item is being repaired
935 if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 )
936 {
937 //if the item has been repaired
938 if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime <= GetWorldTotalMin() )
939 {
940 //A repair item is ready, therefore, return false
941 return( FALSE );
942 }
943 else
944 {
945 fHaveOnlyUnRepairedItems = TRUE;
946 }
947 }
948 }
949 }
950 }
951 }
952
953 return( fHaveOnlyUnRepairedItems );
954 }
955
956
957 static bool DoesItemAppearInDealerInventoryList(ArmsDealerID, UINT16 usItemIndex, BOOLEAN fPurchaseFromPlayer);
958
959
CanDealerTransactItem(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,BOOLEAN const fPurchaseFromPlayer)960 BOOLEAN CanDealerTransactItem(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, BOOLEAN const fPurchaseFromPlayer)
961 {
962 switch ( GetTypeOfArmsDealer(ubArmsDealer) )
963 {
964 case ARMS_DEALER_SELLS_ONLY:
965 if ( fPurchaseFromPlayer )
966 {
967 // this dealer only sells stuff to player, so he can't buy anything from him
968 return( FALSE );
969 }
970 break;
971
972 case ARMS_DEALER_BUYS_ONLY:
973 if ( !fPurchaseFromPlayer )
974 {
975 // this dealer only buys stuff from player, so he can't sell anything to him
976 return( FALSE );
977 }
978 break;
979
980 case ARMS_DEALER_BUYS_SELLS:
981 if (GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::BUYS_EVERYTHING))
982 {
983 if ( fPurchaseFromPlayer )
984 {
985 // these guys will buy nearly anything from the player, regardless of what they carry for sale!
986 return( CalcValueOfItemToDealer( ubArmsDealer, usItemIndex, FALSE ) > 0 );
987 }
988 //else selling inventory uses their inventory list
989 break;
990 }
991 // the others go by their inventory list
992 break;
993
994 case ARMS_DEALER_REPAIRS:
995 // repairmen don't have a complete list of what they'll repair in their inventory,
996 // so we must check the item's properties instead.
997 return( CanDealerRepairItem( ubArmsDealer, usItemIndex ) );
998
999 default:
1000 AssertMsg( FALSE, String( "CanDealerTransactItem(), type of dealer %d. AM 0.", GetDealer(ubArmsDealer)->type) );
1001 return(FALSE);
1002 }
1003
1004 return( DoesItemAppearInDealerInventoryList( ubArmsDealer, usItemIndex, fPurchaseFromPlayer ) );
1005 }
1006
1007
CanDealerRepairItem(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex)1008 BOOLEAN CanDealerRepairItem(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex)
1009 {
1010 UINT32 uiFlags;
1011
1012 uiFlags = GCM->getItem(usItemIndex)->getFlags();
1013
1014 // can't repair anything that's not repairable!
1015 if ( !( uiFlags & ITEM_REPAIRABLE ) )
1016 {
1017 return(FALSE);
1018 }
1019
1020 auto dealer = GetDealer(ubArmsDealer);
1021 if (dealer->type == ArmsDealerType::ARMS_DEALER_REPAIRS)
1022 {
1023 if (dealer->hasFlag(ArmsDealerFlag::REPAIRS_ELECTRONICS))
1024 {
1025 // repairs ONLY electronics
1026 return (uiFlags & ITEM_ELECTRONIC) > 0;
1027 }
1028 else
1029 {
1030 // repairs ANYTHING non-electronic
1031 return (uiFlags & ITEM_ELECTRONIC) == 0;
1032 }
1033 }
1034 else
1035 {
1036 AssertMsg(FALSE, String("CanDealerRepairItem(), Arms Dealer %d is not a recognized repairman!. AM 1.", ubArmsDealer));
1037 }
1038
1039 // can't repair this...
1040 return(FALSE);
1041 }
1042
1043
AllocMemsetSpecialItemArray(DEALER_ITEM_HEADER * const pDealerItem,UINT8 const ubElementsNeeded)1044 static void AllocMemsetSpecialItemArray(DEALER_ITEM_HEADER* const pDealerItem, UINT8 const ubElementsNeeded)
1045 {
1046 Assert(pDealerItem);
1047
1048 pDealerItem->SpecialItem.assign(ubElementsNeeded, DEALER_SPECIAL_ITEM{});
1049 }
1050
1051
ResizeSpecialItemArray(DEALER_ITEM_HEADER * const pDealerItem,UINT8 const ubElementsNeeded)1052 static void ResizeSpecialItemArray(DEALER_ITEM_HEADER* const pDealerItem, UINT8 const ubElementsNeeded)
1053 {
1054 Assert(pDealerItem);
1055
1056 pDealerItem->SpecialItem.resize(ubElementsNeeded, DEALER_SPECIAL_ITEM{});
1057 }
1058
1059
FreeSpecialItemArray(DEALER_ITEM_HEADER * pDealerItem)1060 static void FreeSpecialItemArray(DEALER_ITEM_HEADER* pDealerItem)
1061 {
1062 Assert(pDealerItem);
1063
1064 pDealerItem->SpecialItem.clear();
1065
1066 pDealerItem->ubTotalItems = pDealerItem->ubPerfectItems;
1067
1068 // doesn't effect perfect items, orders or stray bullets!
1069 }
1070
1071
1072 static UINT8 DetermineDealerItemCondition(ArmsDealerID, UINT16 usItemIndex);
1073
1074
ArmsDealerGetsFreshStock(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 const ubNumItems)1075 static void ArmsDealerGetsFreshStock(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 const ubNumItems)
1076 {
1077 UINT8 ubCnt;
1078 UINT8 ubItemCondition;
1079 UINT8 ubPerfectOnes = 0;
1080 SPECIAL_ITEM_INFO SpclItemInfo;
1081
1082 // create item info describing a perfect item
1083 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1084
1085
1086 // determine the condition of each one, counting up new ones, but adding damaged ones right away
1087 for ( ubCnt = 0; ubCnt < ubNumItems; ubCnt++ )
1088 {
1089 ubItemCondition = DetermineDealerItemCondition( ubArmsDealer, usItemIndex);
1090
1091 // if the item is brand new
1092 if ( ubItemCondition == 100)
1093 {
1094 ubPerfectOnes++;
1095 }
1096 else
1097 {
1098 // add a used item with that condition to his inventory
1099 SpclItemInfo.bItemCondition = (INT8) ubItemCondition;
1100 AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, 1 );
1101 }
1102 }
1103
1104 // now add all the perfect ones, in one shot
1105 if ( ubPerfectOnes > 0)
1106 {
1107 SpclItemInfo.bItemCondition = 100;
1108 AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ubPerfectOnes );
1109 }
1110 }
1111
1112
1113 static BOOLEAN ItemContainsLiquid(UINT16 usItemIndex);
1114
1115
DetermineDealerItemCondition(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex)1116 static UINT8 DetermineDealerItemCondition(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex)
1117 {
1118 UINT8 ubCondition = 100;
1119
1120 // if it's a damagable item, and not a liquid (those are always sold full)
1121 if ( ( GCM->getItem(usItemIndex)->getFlags() & ITEM_DAMAGEABLE ) && !ItemContainsLiquid( usItemIndex ) )
1122 {
1123 // if he ONLY has used items, or 50% of the time if he carries both used & new items
1124 if ( ( GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::ONLY_USED_ITEMS) ) ||
1125 ( ( GetDealer(ubArmsDealer)->hasFlag(ArmsDealerFlag::SOME_USED_ITEMS) ) && ( Random( 100 ) < 50 ) ) )
1126 {
1127 // make the item a used one
1128 ubCondition = (UINT8)(20 + Random( 60 ));
1129 }
1130 }
1131
1132 return( ubCondition);
1133 }
1134
1135
ItemContainsLiquid(UINT16 usItemIndex)1136 static BOOLEAN ItemContainsLiquid(UINT16 usItemIndex)
1137 {
1138 switch ( usItemIndex )
1139 {
1140 case CANTEEN:
1141 case BEER:
1142 case ALCOHOL:
1143 case JAR_HUMAN_BLOOD:
1144 case JAR_CREATURE_BLOOD:
1145 case JAR_QUEEN_CREATURE_BLOOD:
1146 case JAR_ELIXIR:
1147 case GAS_CAN:
1148 return( TRUE );
1149 }
1150
1151 return( FALSE );
1152 }
1153
1154
1155 static UINT8 CountActiveSpecialItemsInArmsDealersInventory(ArmsDealerID, UINT16 usItemIndex);
1156
1157
CountDistinctItemsInArmsDealersInventory(ArmsDealerID const ubArmsDealer)1158 UINT32 CountDistinctItemsInArmsDealersInventory(ArmsDealerID const ubArmsDealer)
1159 {
1160 UINT32 uiNumOfItems=0;
1161 UINT16 usItemIndex;
1162
1163
1164 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
1165 {
1166 //if there are any items
1167 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 )
1168 {
1169 // if there are any items in perfect condition
1170 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems > 0 )
1171 {
1172 // if the items can be stacked
1173 // NOTE: This test must match the one inside AddItemsToTempDealerInventory() exactly!
1174 if ( DealerItemIsSafeToStack( usItemIndex ) )
1175 {
1176 // regardless of how many there are, they count as 1 *distinct* item! They will all be together in one box...
1177 uiNumOfItems++;
1178 }
1179 else
1180 {
1181 // non-stacking items must be stored in one / box , because each may have unique fields besides bStatus[]
1182 // Example: guns all have ammo, ammo type, etc. We need these uniquely represented for pricing & manipulation
1183 uiNumOfItems += gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems;
1184 }
1185 }
1186
1187 // each *active* special item counts as one additional distinct item (each one occupied a separate shopkeeper box!)
1188 // NOTE: This is including items being repaired!!!
1189 uiNumOfItems += CountActiveSpecialItemsInArmsDealersInventory( ubArmsDealer, usItemIndex);
1190 }
1191 }
1192
1193 return( uiNumOfItems );
1194 }
1195
1196
CountActiveSpecialItemsInArmsDealersInventory(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex)1197 static UINT8 CountActiveSpecialItemsInArmsDealersInventory(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex)
1198 {
1199 UINT8 ubActiveSpecialItems = 0;
1200 UINT8 ubElement;
1201
1202
1203 // next, try to sell all the used ones, gotta do these one at a time so we can remove them by element
1204 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1205 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1206 {
1207 // don't worry about negative condition, repairmen can't come this far, they don't sell!
1208 if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive )
1209 {
1210 ubActiveSpecialItems++;
1211 }
1212 }
1213
1214 return( ubActiveSpecialItems );
1215 }
1216
1217
1218 static UINT8 CountSpecificItemsRepairDealerHasInForRepairs(ArmsDealerID, UINT16 usItemIndex);
1219
1220
CountTotalItemsRepairDealerHasInForRepairs(ArmsDealerID const ubArmsDealer)1221 UINT16 CountTotalItemsRepairDealerHasInForRepairs(ArmsDealerID const ubArmsDealer)
1222 {
1223 UINT16 usItemIndex;
1224 UINT16 usHowManyInForRepairs = 0;
1225
1226 //if the dealer is not a repair dealer, no need to count, return 0
1227 if( !DoesDealerDoRepairs( ubArmsDealer ) )
1228 return( 0 );
1229
1230 //loop through the dealers inventory and count the number of items in for repairs
1231 for( usItemIndex=0; usItemIndex < MAXITEMS; usItemIndex++ )
1232 {
1233 usHowManyInForRepairs += CountSpecificItemsRepairDealerHasInForRepairs( ubArmsDealer, usItemIndex );
1234 }
1235
1236 return( usHowManyInForRepairs );
1237 }
1238
1239
CountSpecificItemsRepairDealerHasInForRepairs(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex)1240 static UINT8 CountSpecificItemsRepairDealerHasInForRepairs(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex)
1241 {
1242 UINT8 ubElement;
1243 UINT8 ubHowManyInForRepairs = 0;
1244
1245 //if the dealer is not a repair dealer, no need to count, return 0
1246 if( !DoesDealerDoRepairs( ubArmsDealer ) )
1247 return( 0 );
1248
1249
1250 //if there is some items in stock
1251 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems )
1252 {
1253 //loop through the array of items
1254 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1255 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1256 {
1257 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive)
1258 {
1259 //if the item's status is below 0, the item is being repaired
1260 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 )
1261 {
1262 ubHowManyInForRepairs++;
1263 }
1264 }
1265 }
1266 }
1267
1268 return( ubHowManyInForRepairs );
1269 }
1270
1271
1272 static void AddAmmoToArmsDealerInventory(ArmsDealerID, UINT16 usItemIndex, UINT8 ubShotsLeft);
1273
1274
AddObjectToArmsDealerInventory(ArmsDealerID const ubArmsDealer,OBJECTTYPE * const pObject)1275 void AddObjectToArmsDealerInventory(ArmsDealerID const ubArmsDealer, OBJECTTYPE* const pObject)
1276 {
1277 UINT8 ubCnt;
1278 SPECIAL_ITEM_INFO SpclItemInfo;
1279
1280
1281 SetSpecialItemInfoFromObject( &SpclItemInfo, pObject );
1282
1283
1284 // split up all the components of an objecttype and add them as seperate items into the dealer's inventory
1285 switch ( GCM->getItem(pObject->usItem)->getItemClass() )
1286 {
1287 case IC_GUN:
1288 // add the gun (keeps the object's status and imprintID)
1289 // if the gun was jammed, this will forget about the jam (i.e. dealer immediately unjams anything he buys)
1290 AddItemToArmsDealerInventory( ubArmsDealer, pObject->usItem, &SpclItemInfo, 1 );
1291
1292 // if any GunAmmoItem is specified
1293 if( pObject->usGunAmmoItem != NONE)
1294 {
1295 // if it's regular ammo
1296 if( GCM->getItem(pObject->usGunAmmoItem)->getItemClass() == IC_AMMO )
1297 {
1298 // and there are some remaining
1299 if ( pObject->ubGunShotsLeft > 0 )
1300 {
1301 // add the bullets of its remaining ammo
1302 AddAmmoToArmsDealerInventory( ubArmsDealer, pObject->usGunAmmoItem, pObject->ubGunShotsLeft );
1303 }
1304 }
1305 else // assume it's attached ammo (mortar shells, grenades)
1306 {
1307 // add the launchable item (can't be imprinted, or have attachments!)
1308 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1309 SpclItemInfo.bItemCondition = pObject->bGunAmmoStatus;
1310
1311 // if the gun it was in was jammed, get rid of the negative status now
1312 if ( SpclItemInfo.bItemCondition < 0 )
1313 {
1314 SpclItemInfo.bItemCondition *= -1;
1315 }
1316
1317 AddItemToArmsDealerInventory( ubArmsDealer, pObject->usGunAmmoItem, &SpclItemInfo, 1 );
1318 }
1319 }
1320 break;
1321
1322 case IC_AMMO:
1323 // add the contents of each magazine (multiple mags may have vastly different #bullets left)
1324 for ( ubCnt = 0; ubCnt < pObject->ubNumberOfObjects; ubCnt++ )
1325 {
1326 AddAmmoToArmsDealerInventory( ubArmsDealer, pObject->usItem, pObject->ubShotsLeft[ ubCnt ] );
1327 }
1328 break;
1329
1330 default:
1331 // add each object seperately (multiple objects may have vastly different statuses, keep any imprintID)
1332 for ( ubCnt = 0; ubCnt < pObject->ubNumberOfObjects; ubCnt++ )
1333 {
1334 SpclItemInfo.bItemCondition = pObject->bStatus[ ubCnt ];
1335 AddItemToArmsDealerInventory( ubArmsDealer, pObject->usItem, &SpclItemInfo, 1 );
1336 }
1337 break;
1338 }
1339
1340
1341 // loop through any detachable attachments and add them as seperate items
1342 for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
1343 {
1344 if( pObject->usAttachItem[ ubCnt ] != NONE )
1345 {
1346 // ARM: Note: this is only used for selling, not repairs, so attachmentes are seperated when sold to a dealer
1347 // If the attachment is detachable
1348 if (! (GCM->getItem(pObject->usAttachItem[ubCnt])->getFlags() & ITEM_INSEPARABLE ) )
1349 {
1350 // add this particular attachment (they can't be imprinted, or themselves have attachments!)
1351 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1352 SpclItemInfo.bItemCondition = pObject->bAttachStatus[ ubCnt ];
1353 AddItemToArmsDealerInventory( ubArmsDealer, pObject->usAttachItem[ ubCnt ], &SpclItemInfo, 1 );
1354 }
1355 }
1356 }
1357
1358
1359 // nuke the original object to prevent any possible item duplication
1360 *pObject = OBJECTTYPE{};
1361 }
1362
1363
AddAmmoToArmsDealerInventory(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 ubShotsLeft)1364 static void AddAmmoToArmsDealerInventory(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 ubShotsLeft)
1365 {
1366 UINT8 ubMagCapacity;
1367 UINT8 *pubStrayAmmo;
1368 SPECIAL_ITEM_INFO SpclItemInfo;
1369
1370
1371
1372 // Ammo only, please!!!
1373 if (GCM->getItem(usItemIndex)->getItemClass() != IC_AMMO )
1374 {
1375 SLOGA("AddAmmoToArmsDealerInventory: Item isn't Ammo");
1376 return;
1377 }
1378
1379 if ( ubShotsLeft == 0)
1380 {
1381 return;
1382 }
1383
1384
1385 ubMagCapacity = GCM->getItem(usItemIndex)->asAmmo()->capacity;
1386
1387 if ( ubShotsLeft >= ubMagCapacity )
1388 {
1389 // add however many FULL magazines the #shot left represents
1390 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1391 AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ( UINT8 ) ( ubShotsLeft / ubMagCapacity ) );
1392 ubShotsLeft %= ubMagCapacity;
1393 }
1394
1395 // any shots left now are "strays" - not enough to completely fill a magazine of this type
1396 if ( ubShotsLeft > 0 )
1397 {
1398 // handle "stray" ammo - add it to the dealer's stray pile
1399 pubStrayAmmo = &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubStrayAmmo);
1400 *pubStrayAmmo += ubShotsLeft;
1401
1402 // if dealer has accumulated enough stray ammo to make another full magazine, convert it!
1403 if ( *pubStrayAmmo >= ubMagCapacity )
1404 {
1405 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1406 AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ( UINT8 ) ( *pubStrayAmmo / ubMagCapacity ) );
1407 *pubStrayAmmo = *pubStrayAmmo % ubMagCapacity;
1408 }
1409 // I know, I know, this is getting pretty anal... But what the hell, it was easy enough to do. ARM.
1410 }
1411 }
1412
1413
1414 static void AddSpecialItemToArmsDealerInventoryAtElement(ArmsDealerID ubArmsDealer, UINT16 usItemIndex, UINT8 ubElement, SPECIAL_ITEM_INFO* pSpclItemInfo);
1415 static BOOLEAN IsItemInfoSpecial(SPECIAL_ITEM_INFO* pSpclItemInfo);
1416
1417
1418 //Use AddObjectToArmsDealerInventory() instead of this when converting a complex item in OBJECTTYPE format.
AddItemToArmsDealerInventory(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,SPECIAL_ITEM_INFO * const pSpclItemInfo,UINT8 ubHowMany)1419 static void AddItemToArmsDealerInventory(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, SPECIAL_ITEM_INFO* const pSpclItemInfo, UINT8 ubHowMany)
1420 {
1421 UINT8 ubRoomLeft;
1422 UINT8 ubElement;
1423 UINT8 ubElementsToAdd;
1424 BOOLEAN fFoundOne;
1425
1426 Assert( ubHowMany > 0);
1427
1428 ubRoomLeft = 255 - gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems;
1429
1430 if ( ubHowMany > ubRoomLeft)
1431 {
1432 // not enough room to store that many, any extras vanish into thin air!
1433 ubHowMany = ubRoomLeft;
1434 }
1435
1436 if ( ubHowMany == 0)
1437 {
1438 return;
1439 }
1440
1441
1442 // decide whether this item is "special" or not
1443 if ( IsItemInfoSpecial( pSpclItemInfo ) )
1444 {
1445 // Anything that's used/damaged or imprinted is store as a special item in the SpecialItem array,
1446 // exactly one item per element. We (re)allocate memory dynamically as necessary to hold the additional items.
1447
1448 do
1449 {
1450 // search for an already allocated, empty element in the special item array
1451 fFoundOne = FALSE;
1452 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1453 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1454 {
1455 if ( !( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) )
1456 {
1457 //Great! Store it here, then.
1458 AddSpecialItemToArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement, pSpclItemInfo );
1459 fFoundOne = TRUE;
1460 break;
1461 }
1462 }
1463
1464 // if we didn't find any inactive elements already allocated
1465 if (!fFoundOne)
1466 {
1467 // then we're going to have to allocate some more space...
1468 ubElementsToAdd = MAX( SPECIAL_ITEMS_ALLOCED_AT_ONCE, ubHowMany);
1469
1470 Assert(gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem.size() + ubElementsToAdd <= UINT8_MAX);
1471 ResizeSpecialItemArray(&gArmsDealersInventory[ubArmsDealer][usItemIndex], static_cast<UINT8>(gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem.size() + ubElementsToAdd));
1472
1473 // now add the special item at the first of the newly added elements (still stored in ubElement!)
1474 AddSpecialItemToArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement, pSpclItemInfo );
1475 }
1476
1477 // store the # of the element it was placed in globally so anyone who needs that can grab it there
1478 gubLastSpecialItemAddedAtElement = ubElement;
1479
1480 ubHowMany--;
1481 } while ( ubHowMany > 0);
1482 }
1483 else // adding perfect item(s)
1484 {
1485 // then it's stored as a "perfect" item, simply add it to that counter!
1486 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems += ubHowMany;
1487 // increase total items of this type
1488 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems += ubHowMany;
1489 }
1490 }
1491
1492
AddSpecialItemToArmsDealerInventoryAtElement(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 const ubElement,SPECIAL_ITEM_INFO * const pSpclItemInfo)1493 static void AddSpecialItemToArmsDealerInventoryAtElement(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 const ubElement, SPECIAL_ITEM_INFO* const pSpclItemInfo)
1494 {
1495 Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems < 255 );
1496 Assert( ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() );
1497 Assert(!gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[ubElement].fActive);
1498 Assert( IsItemInfoSpecial( pSpclItemInfo ) );
1499
1500
1501 //Store the special values in that element, and make it active
1502 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive = TRUE;
1503
1504 gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[ubElement].Info = *pSpclItemInfo;
1505
1506 // increase the total items
1507 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems++;
1508 }
1509
1510
1511 // removes ubHowMany items of usItemIndex with the matching Info from dealer ubArmsDealer
RemoveItemFromArmsDealerInventory(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,SPECIAL_ITEM_INFO * const pSpclItemInfo,UINT8 ubHowMany)1512 void RemoveItemFromArmsDealerInventory(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, SPECIAL_ITEM_INFO* const pSpclItemInfo, UINT8 ubHowMany)
1513 {
1514 DEALER_SPECIAL_ITEM *pSpecialItem;
1515 UINT8 ubElement;
1516
1517
1518 Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
1519
1520 if ( ubHowMany == 0)
1521 {
1522 return;
1523 }
1524
1525
1526 // decide whether this item is "special" or not
1527 if ( IsItemInfoSpecial( pSpclItemInfo ) )
1528 {
1529 // look through the elements, trying to find special items matching the specifications
1530 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1531 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1532 {
1533 pSpecialItem = &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ]);
1534
1535 // if this element is in use
1536 if ( pSpecialItem->fActive )
1537 {
1538 // and its contents are exactly what we're looking for
1539 if( memcmp( &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info), pSpclItemInfo, sizeof( SPECIAL_ITEM_INFO ) ) == 0 )
1540 {
1541 // Got one! Remove it
1542 RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement );
1543
1544 ubHowMany--;
1545 if ( ubHowMany == 0)
1546 {
1547 break;
1548 }
1549 }
1550 }
1551 }
1552
1553 // when we've searched all the special item elements, we'd better not have any more items to remove!
1554 Assert( ubHowMany == 0);
1555 }
1556 else // removing perfect item(s)
1557 {
1558 // then it's stored as a "perfect" item, simply subtract from tha counter!
1559 Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems );
1560 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems -= ubHowMany;
1561 // decrease total items of this type
1562 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems -= ubHowMany;
1563 }
1564 }
1565
1566
RemoveRandomItemFromArmsDealerInventory(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 ubHowMany)1567 static void RemoveRandomItemFromArmsDealerInventory(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 ubHowMany)
1568 {
1569 UINT8 ubWhichOne;
1570 UINT8 ubSkippedAlready;
1571 BOOLEAN fFoundIt;
1572 UINT8 ubElement;
1573 SPECIAL_ITEM_INFO SpclItemInfo;
1574
1575
1576 // not permitted for repair dealers - would take extra code to subtract items under repair from ubTotalItems!!!
1577 Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
1578 // Can't remove any items in for repair, though!
1579 Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
1580
1581 while ( ubHowMany > 0)
1582 {
1583 // pick a random one to get rid of
1584 ubWhichOne = (UINT8)Random(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
1585
1586 // if we picked one of the perfect ones...
1587 if ( ubWhichOne < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems )
1588 {
1589 // create item info describing a perfect item
1590 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1591 // then that's easy, its condition is 100, so remove one of those
1592 RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, 1 );
1593 }
1594 else
1595 {
1596 // Yikes! Gotta look through the special items. We already know it's not any of the perfect ones, subtract those
1597 ubWhichOne -= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems;
1598 ubSkippedAlready = 0;
1599
1600 fFoundIt = FALSE;
1601
1602 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1603 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1604 {
1605 // if this is an active special item, not in repair
1606 if (gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[ubElement].fActive) // &&
1607 //(gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[ubElement].Info.bItemCondition > 0))
1608 {
1609 // if we skipped the right amount of them
1610 if ( ubSkippedAlready == ubWhichOne )
1611 {
1612 // then this one is it! That's the one we're gonna remove
1613 RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement );
1614 fFoundIt = TRUE;
1615 break;
1616 }
1617 else
1618 {
1619 // keep looking...
1620 ubSkippedAlready++;
1621 }
1622 }
1623 }
1624
1625 // this HAS to work, or the data structure is corrupt!
1626 Assert(fFoundIt);
1627 }
1628
1629 ubHowMany--;
1630 }
1631 }
1632
1633
RemoveSpecialItemFromArmsDealerInventoryAtElement(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,UINT8 const ubElement)1634 void RemoveSpecialItemFromArmsDealerInventoryAtElement(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, UINT8 const ubElement)
1635 {
1636 Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 );
1637 Assert( ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() );
1638 Assert(gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[ubElement].fActive);
1639
1640 // wipe it out (turning off fActive)
1641 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ] = DEALER_SPECIAL_ITEM{};
1642
1643 // one fewer item remains...
1644 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems--;
1645 }
1646
1647
1648
AddDeadArmsDealerItemsToWorld(SOLDIERTYPE const * const pSoldier)1649 BOOLEAN AddDeadArmsDealerItemsToWorld(SOLDIERTYPE const* const pSoldier)
1650 {
1651 UINT16 usItemIndex;
1652 UINT8 ubElement;
1653 UINT8 ubHowManyMaxAtATime;
1654 UINT8 ubLeftToDrop;
1655 UINT8 ubNowDropping;
1656 OBJECTTYPE TempObject;
1657 DEALER_SPECIAL_ITEM *pSpecialItem;
1658 SPECIAL_ITEM_INFO SpclItemInfo;
1659
1660 //Get Dealer ID from from merc Id
1661 ArmsDealerID const bArmsDealer = GetArmsDealerIDFromMercID(pSoldier->ubProfile);
1662 if (bArmsDealer == ARMS_DEALER_INVALID)
1663 {
1664 // not a dealer, that's ok, we get called for every dude that croaks.
1665 return( FALSE );
1666 }
1667
1668
1669 // mark the dealer as being out of business!
1670 gArmsDealerStatus[ bArmsDealer ].fOutOfBusiness = TRUE;
1671
1672 //loop through all the items in the dealer's inventory, and drop them all where the dealer was set up.
1673
1674 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
1675 {
1676 //if the dealer has any items of this type
1677 if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubTotalItems > 0)
1678 {
1679 // if he has any perfect items of this time
1680 if ( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems > 0 )
1681 {
1682 // drop all the perfect items first
1683
1684 // drop stackable items like ammo in stacks of whatever will fit into a large pocket instead of one at a time
1685 ubHowManyMaxAtATime = ItemSlotLimit( usItemIndex, BIGPOCK1POS );
1686 if ( ubHowManyMaxAtATime < 1 )
1687 {
1688 ubHowManyMaxAtATime = 1;
1689 }
1690
1691 // create item info describing a perfect item
1692 SetSpecialItemInfoToDefaults( &SpclItemInfo );
1693
1694 ubLeftToDrop = gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems;
1695
1696 // ATE: While it IS leagal here to use pSoldier->sInitialGridNo, cause of where this
1697 // function is called, there are times when we're not guarenteed that sGridNo is good
1698 while ( ubLeftToDrop > 0)
1699 {
1700 ubNowDropping = MIN( ubLeftToDrop, ubHowManyMaxAtATime );
1701
1702 MakeObjectOutOfDealerItems( usItemIndex, &SpclItemInfo, &TempObject, ubNowDropping );
1703 AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 );
1704
1705 ubLeftToDrop -= ubNowDropping;
1706 }
1707
1708 // remove them all from his inventory
1709 RemoveItemFromArmsDealerInventory( bArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems );
1710 }
1711
1712 // then drop all the special items
1713 Assert(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1714 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1715 {
1716 pSpecialItem = &(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ]);
1717
1718 if ( pSpecialItem->fActive )
1719 {
1720 MakeObjectOutOfDealerItems(usItemIndex, &(pSpecialItem->Info), &TempObject, 1 );
1721 AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 );
1722 RemoveItemFromArmsDealerInventory( bArmsDealer, usItemIndex, &(pSpecialItem->Info), 1 );
1723 }
1724 }
1725
1726 // release any memory allocated for special items, he won't need it now...
1727 if (gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem.size() > 0)
1728 {
1729 FreeSpecialItemArray( &gArmsDealersInventory[ bArmsDealer ][ usItemIndex ] );
1730 }
1731 }
1732 }
1733
1734 //if the dealer has money
1735 if( gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash > 0 )
1736 {
1737 CreateMoney(gArmsDealerStatus[bArmsDealer].uiArmsDealersCash, &TempObject);
1738
1739 //add the money item to the dealers feet
1740 AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 );
1741
1742 gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash = 0;
1743 }
1744
1745
1746 return( TRUE );
1747 }
1748
1749
MakeObjectOutOfDealerItems(UINT16 usItemIndex,SPECIAL_ITEM_INFO * pSpclItemInfo,OBJECTTYPE * pObject,UINT8 ubHowMany)1750 void MakeObjectOutOfDealerItems( UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, OBJECTTYPE *pObject, UINT8 ubHowMany )
1751 {
1752 INT8 bItemCondition;
1753 UINT8 ubCnt;
1754
1755
1756 bItemCondition = pSpclItemInfo->bItemCondition;
1757
1758 //if the item condition is below 0, the item is in for repairs, so flip the sign
1759 if( bItemCondition < 0 )
1760 {
1761 bItemCondition *= -1;
1762 }
1763
1764 CreateItems( usItemIndex, bItemCondition, ubHowMany, pObject );
1765
1766 // set the ImprintID
1767 pObject->ubImprintID = pSpclItemInfo->ubImprintID;
1768
1769 // add any attachments we've been storing
1770 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
1771 {
1772 if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE )
1773 {
1774 // store what it is, and its condition
1775 pObject->usAttachItem[ ubCnt ] = pSpclItemInfo->usAttachment[ ubCnt ];
1776 pObject->bAttachStatus[ ubCnt ] = pSpclItemInfo->bAttachmentStatus[ ubCnt ];
1777 }
1778 }
1779
1780 // if it's a gun
1781 if (GCM->getItem(pObject->usItem)->getItemClass() == IC_GUN )
1782 {
1783 // Empty out the bullets put in by CreateItem(). We now sell all guns empty of bullets. This is so that we don't
1784 // have to keep track of #bullets in a gun throughout dealer inventory. Without this, players could "reload" guns
1785 // they don't have ammo for by selling them to Tony & buying them right back fully loaded! One could repeat this
1786 // ad nauseum (empty the gun between visits) as a (really expensive) way to get unlimited special ammo like rockets.
1787 pObject->ubGunShotsLeft = 0;
1788 }
1789 }
1790
1791
1792 static void GiveItemToArmsDealerforRepair(ArmsDealerID, UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, UINT8 ubOwnerProfileId);
1793
1794
GiveObjectToArmsDealerForRepair(ArmsDealerID const ubArmsDealer,OBJECTTYPE const * const pObject,UINT8 const ubOwnerProfileId)1795 void GiveObjectToArmsDealerForRepair(ArmsDealerID const ubArmsDealer, OBJECTTYPE const* const pObject, UINT8 const ubOwnerProfileId)
1796 {
1797 //UINT8 ubCnt;
1798 SPECIAL_ITEM_INFO SpclItemInfo;
1799
1800
1801 Assert( DoesDealerDoRepairs( ubArmsDealer ) );
1802
1803 // Any object passed into here must already be:
1804 // a) Unstacked
1805 Assert( pObject->ubNumberOfObjects == 1 );
1806
1807 // b) Repairable
1808 Assert( CanDealerRepairItem( ubArmsDealer, pObject->usItem ) );
1809
1810 // c) Actually damaged, or a rocket rifle (being reset)
1811 Assert( ( pObject->bStatus[ 0 ] < 100 ) || ItemIsARocketRifle( pObject->usItem ) );
1812
1813 /* ARM: Can now repair with removeable attachments still attached...
1814 // d) Already stripped of all *detachable* attachments
1815 for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
1816 {
1817 if ( pObject->usAttachItem[ ubCnt ] != NONE )
1818 {
1819 // If the attachment is detachable
1820 if (! (GCM->getItem(pObject->usAttachItem[ubCnt])->getFlags() & ITEM_INSEPARABLE ) )
1821 {
1822 SLOGA("GiveObjectToArmsDealerForRepair: something wrong with attachments");
1823 }
1824 }
1825 }*/
1826
1827 // e) If a gun, stripped of any non-ammo-class GunAmmoItems, and bullets
1828 if (GCM->getItem(pObject->usItem)->getItemClass() == IC_GUN )
1829 {
1830 // if any GunAmmoItem is specified
1831 if( pObject->usGunAmmoItem != NONE)
1832 {
1833 // it better be regular ammo, and empty
1834 Assert( GCM->getItem(pObject->usGunAmmoItem)->getItemClass() == IC_AMMO );
1835 Assert( pObject->ubGunShotsLeft == 0 );
1836 }
1837 }
1838
1839
1840 SetSpecialItemInfoFromObject( &SpclItemInfo, pObject );
1841
1842 // ok, given all that, now everything is easy!
1843 // if the gun was jammed, this will forget about the jam (i.e. dealer immediately unjams anything he will be repairing)
1844 GiveItemToArmsDealerforRepair( ubArmsDealer, pObject->usItem, &SpclItemInfo, ubOwnerProfileId );
1845 }
1846
1847
1848 static UINT32 CalculateSpecialItemRepairTime(ArmsDealerID, UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo);
1849 static UINT32 WhenWillRepairmanBeAllDoneRepairing(ArmsDealerID);
1850
1851
1852 //PLEASE: Use GiveObjectToArmsDealerForRepair() instead of this when repairing a item in OBJECTTYPE format.
GiveItemToArmsDealerforRepair(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,SPECIAL_ITEM_INFO * const pSpclItemInfo,UINT8 const ubOwnerProfileId)1853 static void GiveItemToArmsDealerforRepair(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, SPECIAL_ITEM_INFO* const pSpclItemInfo, UINT8 const ubOwnerProfileId)
1854 {
1855 UINT32 uiTimeWhenFreeToStartIt;
1856 UINT32 uiMinutesToFix;
1857 UINT32 uiMinutesShopClosedBeforeItsDone;
1858 UINT32 uiDoneWhen;
1859
1860
1861 Assert( DoesDealerDoRepairs( ubArmsDealer ) );
1862 Assert( pSpclItemInfo->bItemCondition > 0 );
1863 Assert( ( pSpclItemInfo->bItemCondition < 100 ) || ItemIsARocketRifle( usItemIndex ) );
1864
1865 // figure out the earliest the repairman will be free to start repairing this item
1866 uiTimeWhenFreeToStartIt = WhenWillRepairmanBeAllDoneRepairing( ubArmsDealer );
1867
1868 //Determine how long it will take to fix
1869 uiMinutesToFix = CalculateSpecialItemRepairTime( ubArmsDealer, usItemIndex, pSpclItemInfo );
1870
1871 uiMinutesShopClosedBeforeItsDone = CalculateOvernightRepairDelay( ubArmsDealer, uiTimeWhenFreeToStartIt, uiMinutesToFix );
1872
1873 // clock time when this will finally be ready
1874 uiDoneWhen = uiTimeWhenFreeToStartIt + uiMinutesToFix + uiMinutesShopClosedBeforeItsDone;
1875
1876 // Negate the status
1877 pSpclItemInfo->bItemCondition *= -1;
1878
1879 // give it to the dealer
1880 AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, pSpclItemInfo, 1 );
1881
1882 //Set the time at which item will be fixed
1883 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ gubLastSpecialItemAddedAtElement ].uiRepairDoneTime = uiDoneWhen;
1884 //Remember the original owner of the item
1885 gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ gubLastSpecialItemAddedAtElement ].ubOwnerProfileId = ubOwnerProfileId;
1886 }
1887
1888
WhenWillRepairmanBeAllDoneRepairing(ArmsDealerID const ubArmsDealer)1889 static UINT32 WhenWillRepairmanBeAllDoneRepairing(ArmsDealerID const ubArmsDealer)
1890 {
1891 UINT32 uiWhenFree;
1892 UINT16 usItemIndex;
1893 UINT8 ubElement;
1894
1895 Assert( DoesDealerDoRepairs( ubArmsDealer ) );
1896
1897 // if nothing is in for repairs, he'll be free RIGHT NOW!
1898 uiWhenFree = GetWorldTotalMin();
1899
1900 //loop through the dealers inventory
1901 for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
1902 {
1903 //if there is some items in stock
1904 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 )
1905 {
1906 Assert(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
1907 for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size()); ubElement++)
1908 {
1909 if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive )
1910 {
1911 //if the item is in for repairs
1912 if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 )
1913 {
1914 // if this item will be done later than the latest we've found so far
1915 if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime > uiWhenFree )
1916 {
1917 // then we're busy til then!
1918 uiWhenFree = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime;
1919 }
1920 }
1921 }
1922 }
1923 }
1924 }
1925
1926 return( uiWhenFree );
1927 }
1928
1929
1930 static UINT32 CalculateSimpleItemRepairTime(ArmsDealerID, UINT16 usItemIndex, INT8 bItemCondition);
1931
1932
CalculateSpecialItemRepairTime(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,SPECIAL_ITEM_INFO * const pSpclItemInfo)1933 static UINT32 CalculateSpecialItemRepairTime(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, SPECIAL_ITEM_INFO* const pSpclItemInfo)
1934 {
1935 UINT32 uiRepairTime;
1936 UINT8 ubCnt;
1937
1938 uiRepairTime = CalculateSimpleItemRepairTime( ubArmsDealer, usItemIndex, pSpclItemInfo->bItemCondition );
1939
1940 // add time to repair any attachments on it
1941 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
1942 {
1943 if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE )
1944 {
1945 // if damaged and repairable
1946 if ( ( pSpclItemInfo->bAttachmentStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ] ) )
1947 {
1948 uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ], pSpclItemInfo->bAttachmentStatus[ ubCnt ] );
1949 }
1950 }
1951 }
1952
1953 return( uiRepairTime );
1954 }
1955
1956
CalculateObjectItemRepairTime(ArmsDealerID const ubArmsDealer,OBJECTTYPE const * const pItemObject)1957 UINT32 CalculateObjectItemRepairTime(ArmsDealerID const ubArmsDealer, OBJECTTYPE const* const pItemObject)
1958 {
1959 UINT32 uiRepairTime;
1960 UINT8 ubCnt;
1961
1962 uiRepairTime = CalculateSimpleItemRepairTime( ubArmsDealer, pItemObject->usItem, pItemObject->bStatus[ 0 ] );
1963
1964 // add time to repair any attachments on it
1965 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
1966 {
1967 if ( pItemObject->usAttachItem[ ubCnt ] != NONE )
1968 {
1969 // if damaged and repairable
1970 if ( ( pItemObject->bAttachStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ] ) )
1971 {
1972 uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ], pItemObject->bAttachStatus[ ubCnt ] );
1973 }
1974 }
1975 }
1976
1977 return( uiRepairTime );
1978 }
1979
1980
1981 static UINT32 CalculateSimpleItemRepairCost(ArmsDealerID, UINT16 usItemIndex, INT8 bItemCondition);
1982
1983
CalculateSimpleItemRepairTime(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,INT8 const bItemCondition)1984 static UINT32 CalculateSimpleItemRepairTime(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, INT8 const bItemCondition)
1985 {
1986 UINT32 uiTimeToRepair = 0;
1987 UINT32 uiRepairCost = 0;
1988
1989 Assert( DoesDealerDoRepairs( ubArmsDealer ) );
1990
1991 // first calc what he'll charge - that takes care of item condition, repair ease, and his repair cost "markup"
1992 uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, usItemIndex, bItemCondition );
1993
1994 // Now adjust that for the repairman's individual repair speed.
1995 // For a repairman, his BUY modifier controls his REPAIR SPEED (1.0 means minutes to repair = price in $)
1996 // with a REPAIR SPEED of 1.0, typical gun price of $2000, and a REPAIR COST of 0.5 this works out to 16.6 hrs
1997 // for a full 100% status repair... Not bad.
1998 uiTimeToRepair = (UINT32)(uiRepairCost * GetDealer(ubArmsDealer)->repairSpeed);
1999
2000 // repairs on electronic items take twice as long if the guy doesn't have the skill
2001 // for dealers, this means anyone but Fredo the Electronics guy takes twice as long (but doesn't charge double)
2002 // (Mind you, current he's the ONLY one who CAN repair Electronics at all! Oh well.)
2003 if( ( GCM->getItem(usItemIndex)->getFlags() & ITEM_ELECTRONIC ) && ( ubArmsDealer != ARMS_DEALER_FREDO ) )
2004 {
2005 uiTimeToRepair *= 2;
2006 }
2007
2008 // avoid "instant" repairs on really cheap, barely damaged crap...
2009 if (uiTimeToRepair < MIN_REPAIR_TIME_IN_MINUTES)
2010 {
2011 uiTimeToRepair = MIN_REPAIR_TIME_IN_MINUTES;
2012 }
2013
2014 return( uiTimeToRepair );
2015 }
2016
2017
CalculateObjectItemRepairCost(ArmsDealerID const ubArmsDealer,OBJECTTYPE const * const pItemObject)2018 UINT32 CalculateObjectItemRepairCost(ArmsDealerID const ubArmsDealer, OBJECTTYPE const* const pItemObject)
2019 {
2020 UINT32 uiRepairCost;
2021 UINT8 ubCnt;
2022
2023 uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, pItemObject->usItem, pItemObject->bStatus[ 0 ] );
2024
2025 // add cost of repairing any attachments on it
2026 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
2027 {
2028 if ( pItemObject->usAttachItem[ ubCnt ] != NONE )
2029 {
2030 // if damaged and repairable
2031 if ( ( pItemObject->bAttachStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ] ) )
2032 {
2033 uiRepairCost += CalculateSimpleItemRepairCost( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ], pItemObject->bAttachStatus[ ubCnt ] );
2034 }
2035 }
2036 }
2037
2038 return( uiRepairCost );
2039 }
2040
2041
CalculateSimpleItemRepairCost(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,INT8 const bItemCondition)2042 static UINT32 CalculateSimpleItemRepairCost(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, INT8 const bItemCondition)
2043 {
2044 UINT32 uiItemCost = 0;
2045 UINT32 uiRepairCost = 0;
2046 INT16 sRepairCostAdj = 0;
2047 //UINT32 uiDifFrom10=0;
2048
2049 // figure out the full value of the item, modified by this dealer's personal Sell (i.e. repair cost) modifier
2050 // don't use CalcShopKeeperItemPrice - we want FULL value!!!
2051 uiItemCost = (UINT32)(GCM->getItem(usItemIndex)->getPrice() * GetDealer(ubArmsDealer)->repairCost);
2052
2053 // get item's repair ease, for each + point is 10% easier, each - point is 10% harder to repair
2054 sRepairCostAdj = 100 - ( 10 * GCM->getItem(usItemIndex)->getRepairEase() );
2055
2056 // make sure it ain't somehow gone too low!
2057 if (sRepairCostAdj < 10)
2058 {
2059 sRepairCostAdj = 10;
2060 }
2061
2062 // calculate repair cost, the more broken it is the more it costs, and the difficulty of repair it is also a factor
2063 uiRepairCost = (UINT32)( uiItemCost * ( sRepairCostAdj * (100 - bItemCondition) / ((FLOAT)100 * 100) ));
2064
2065 /*
2066 //if the price is not diviseble by 10, make it so
2067 uiDifFrom10 = 10 - uiRepairCost % 10;
2068 if( uiDifFrom10 != 0 )
2069 {
2070 uiRepairCost += uiDifFrom10;
2071 }*/
2072
2073 if ( ItemIsARocketRifle( usItemIndex ) )
2074 {
2075 // resetting imprinting for a rocket rifle costs something extra even if rifle is at 100%
2076 uiRepairCost += 100;
2077 }
2078
2079
2080 // anything repairable has to have a minimum price
2081 if ( uiRepairCost < MIN_REPAIR_COST )
2082 {
2083 uiRepairCost = MIN_REPAIR_COST;
2084 }
2085
2086 return( uiRepairCost );
2087 }
2088
2089
2090
SetSpecialItemInfoToDefaults(SPECIAL_ITEM_INFO * pSpclItemInfo)2091 void SetSpecialItemInfoToDefaults( SPECIAL_ITEM_INFO *pSpclItemInfo )
2092 {
2093 UINT8 ubCnt;
2094
2095 *pSpclItemInfo = SPECIAL_ITEM_INFO{};
2096
2097 pSpclItemInfo->bItemCondition = 100;
2098 pSpclItemInfo->ubImprintID = NO_PROFILE;
2099
2100 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
2101 {
2102 pSpclItemInfo->usAttachment[ ubCnt ] = NONE;
2103 pSpclItemInfo->bAttachmentStatus[ ubCnt ] = 0;
2104 }
2105 }
2106
2107
SetSpecialItemInfoFromObject(SPECIAL_ITEM_INFO * pSpclItemInfo,const OBJECTTYPE * pObject)2108 void SetSpecialItemInfoFromObject(SPECIAL_ITEM_INFO* pSpclItemInfo, const OBJECTTYPE* pObject)
2109 {
2110 UINT8 ubCnt;
2111
2112
2113 *pSpclItemInfo = SPECIAL_ITEM_INFO{};
2114
2115
2116 if( GCM->getItem(pObject->usItem)->getItemClass() == IC_AMMO )
2117 {
2118 // ammo condition is always 100, don't use status, which holds the #bullets
2119 pSpclItemInfo->bItemCondition = 100;
2120 }
2121 else
2122 {
2123 pSpclItemInfo->bItemCondition = pObject->bStatus[ 0 ];
2124 }
2125
2126 // only guns currently have imprintID properly initialized...
2127 if ( GCM->getItem(pObject->usItem)->getItemClass() == IC_GUN)
2128 {
2129 pSpclItemInfo->ubImprintID = pObject->ubImprintID;
2130 }
2131 else
2132 {
2133 // override garbage imprintIDs (generally 0) for non-guns
2134 pSpclItemInfo->ubImprintID = NO_PROFILE;
2135 }
2136
2137 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
2138 {
2139 if( pObject->usAttachItem[ ubCnt ] != NONE )
2140 {
2141 // store what it is
2142 pSpclItemInfo->usAttachment[ ubCnt ] = pObject->usAttachItem[ ubCnt ];
2143 pSpclItemInfo->bAttachmentStatus[ ubCnt ] = pObject->bAttachStatus[ ubCnt ];
2144 }
2145 else
2146 {
2147 pSpclItemInfo->usAttachment[ ubCnt ] = NONE;
2148 pSpclItemInfo->bAttachmentStatus[ ubCnt ] = 0;
2149 }
2150 }
2151 }
2152
2153
IsItemInfoSpecial(SPECIAL_ITEM_INFO * pSpclItemInfo)2154 static BOOLEAN IsItemInfoSpecial(SPECIAL_ITEM_INFO* pSpclItemInfo)
2155 {
2156 UINT8 ubCnt;
2157
2158
2159 // being damaged / in repairs makes an item special
2160 if ( pSpclItemInfo->bItemCondition != 100 )
2161 {
2162 return(TRUE);
2163 }
2164
2165 // being imprinted makes an item special
2166 if (pSpclItemInfo->ubImprintID != NO_PROFILE)
2167 {
2168 return(TRUE);
2169 }
2170
2171 // having an attachment makes an item special
2172 for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
2173 {
2174 if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE )
2175 {
2176 return(TRUE);
2177 }
2178 }
2179
2180 // otherwise, it's just a "perfect" item, nothing special about it
2181 return(FALSE);
2182 }
2183
2184
DoesItemAppearInDealerInventoryList(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,BOOLEAN const fPurchaseFromPlayer)2185 static bool DoesItemAppearInDealerInventoryList(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, BOOLEAN const fPurchaseFromPlayer)
2186 {
2187 if(GCM->getDealerInventory(ubArmsDealer)->hasItem(GCM->getItem(usItemIndex)))
2188 {
2189 int maxAmount = GetDealersMaxItemAmount(ubArmsDealer, usItemIndex);
2190 return (maxAmount > 0) || fPurchaseFromPlayer;
2191 }
2192 else
2193 {
2194 return false;
2195 }
2196 }
2197
2198
CalcValueOfItemToDealer(ArmsDealerID const ubArmsDealer,UINT16 const usItemIndex,BOOLEAN const fDealerSelling)2199 UINT16 CalcValueOfItemToDealer(ArmsDealerID const ubArmsDealer, UINT16 const usItemIndex, BOOLEAN const fDealerSelling)
2200 {
2201 UINT16 usBasePrice;
2202 UINT8 ubItemPriceClass;
2203 UINT8 ubDealerPriceClass;
2204 UINT16 usValueToThisDealer;
2205
2206
2207 usBasePrice = GCM->getItem(usItemIndex)->getPrice();
2208
2209 if ( usBasePrice == 0 )
2210 {
2211 // worthless to any dealer
2212 return( 0 );
2213 }
2214
2215
2216 // figure out the price class this dealer prefers
2217 switch ( ubArmsDealer )
2218 {
2219 case ARMS_DEALER_JAKE:
2220 ubDealerPriceClass = PRICE_CLASS_JUNK;
2221 break;
2222 case ARMS_DEALER_KEITH:
2223 ubDealerPriceClass = PRICE_CLASS_CHEAP;
2224 break;
2225 case ARMS_DEALER_FRANZ:
2226 ubDealerPriceClass = PRICE_CLASS_EXPENSIVE;
2227 break;
2228
2229 // other dealers don't use this system
2230 default:
2231 if ( DoesItemAppearInDealerInventoryList( ubArmsDealer, usItemIndex, TRUE ) )
2232 {
2233 return( usBasePrice );
2234 }
2235 else
2236 {
2237 return( 0 );
2238 }
2239 }
2240
2241
2242 // the rest of this function applies only to the "general" dealers ( Jake, Keith, and Franz )
2243
2244 // Micky & Gabby specialize in creature parts & such, the others don't buy these at all (exception: jars)
2245 if ((usItemIndex != JAR) &&
2246 (DoesItemAppearInDealerInventoryList(ARMS_DEALER_MICKY, usItemIndex, TRUE) ||
2247 DoesItemAppearInDealerInventoryList(ARMS_DEALER_GABBY, usItemIndex, TRUE)))
2248 {
2249 return( 0 );
2250 }
2251
2252 if ( ( ubArmsDealer == ARMS_DEALER_KEITH ) && ( GCM->getItem(usItemIndex)->getItemClass() & ( IC_GUN | IC_LAUNCHER ) ) )
2253 {
2254 // Keith won't buy guns until the Hillbillies are vanquished
2255 if (!CheckFact(FACT_HILLBILLIES_KILLED, KEITH))
2256 {
2257 return( 0 );
2258 }
2259 }
2260
2261
2262 // figure out which price class it belongs to
2263 if ( usBasePrice < 100 )
2264 {
2265 ubItemPriceClass = PRICE_CLASS_JUNK;
2266 }
2267 else
2268 if ( usBasePrice < 1000 )
2269 {
2270 ubItemPriceClass = PRICE_CLASS_CHEAP;
2271 }
2272 else
2273 {
2274 ubItemPriceClass = PRICE_CLASS_EXPENSIVE;
2275 }
2276
2277
2278 if( !fDealerSelling )
2279 {
2280 // junk dealer won't buy expensive stuff at all, expensive dealer won't buy junk at all
2281 if ( ABS( (INT8) ubDealerPriceClass - (INT8) ubItemPriceClass ) == 2 )
2282 {
2283 return( 0 );
2284 }
2285 }
2286
2287 // start with the base price
2288 usValueToThisDealer = usBasePrice;
2289
2290 // if it's out of their preferred price class
2291 if ( ubDealerPriceClass != ubItemPriceClass )
2292 {
2293 // exception: Gas (Jake's)
2294 if ( usItemIndex != GAS_CAN )
2295 {
2296 // they pay only 1/3 of true value!
2297 usValueToThisDealer /= 3;
2298 }
2299 }
2300
2301
2302 // Tony specializes in guns, weapons, and ammo, so make others pay much less for that kind of stuff
2303 if ( DoesItemAppearInDealerInventoryList( ARMS_DEALER_TONY, usItemIndex, TRUE ) )
2304 {
2305 // others pay only 1/2 of that value!
2306 usValueToThisDealer /= 2;
2307 }
2308
2309
2310 // minimum bet $1 !
2311 if ( usValueToThisDealer == 0 )
2312 {
2313 usValueToThisDealer = 1;
2314 }
2315
2316 return( usValueToThisDealer );
2317 }
2318
2319
DealerItemIsSafeToStack(UINT16 usItemIndex)2320 BOOLEAN DealerItemIsSafeToStack( UINT16 usItemIndex )
2321 {
2322 // basically any item type with nothing unique about it besides its status can be stacked in dealer's inventory boxes...
2323 // NOTE: This test is only applied to items already KNOWN to be perfect - special items are obviously not-stackable
2324
2325 if ( GCM->getItem(usItemIndex)->getItemClass() == IC_GUN )
2326 {
2327 return( FALSE );
2328 }
2329
2330 /*
2331 if ( ItemSlotLimit( usItemIndex, BIGPOCK1POS ) > 1 )
2332 {
2333 return( TRUE );
2334 }*/
2335
2336 return( TRUE );
2337 }
2338
2339
GuaranteeMinimumAlcohol(ArmsDealerID const ubArmsDealer)2340 static void GuaranteeMinimumAlcohol(ArmsDealerID const ubArmsDealer)
2341 {
2342 GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, BEER, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, BEER ) / 3 ) );
2343 GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, WINE, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, WINE ) / 3 ) );
2344 GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, ALCOHOL, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, ALCOHOL ) / 3 ) );
2345 }
2346
2347
ItemIsARocketRifle(INT16 sItemIndex)2348 BOOLEAN ItemIsARocketRifle(INT16 sItemIndex)
2349 {
2350 if ( ( sItemIndex == ROCKET_RIFLE ) || ( sItemIndex == AUTO_ROCKET_RIFLE ) )
2351 {
2352 return( TRUE );
2353 }
2354 else
2355 {
2356 return( FALSE );
2357 }
2358 }
2359
2360
GetArmsDealerShopHours(ArmsDealerID const ubArmsDealer,UINT32 * const puiOpeningTime,UINT32 * const puiClosingTime)2361 static BOOLEAN GetArmsDealerShopHours(ArmsDealerID const ubArmsDealer, UINT32* const puiOpeningTime, UINT32* const puiClosingTime)
2362 {
2363 SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(GetDealer(ubArmsDealer)->profileID);
2364 if ( pSoldier == NULL )
2365 {
2366 return( FALSE );
2367 }
2368
2369 if (!ExtractScheduleDoorLockAndUnlockInfo(pSoldier, puiOpeningTime, puiClosingTime))
2370 {
2371 return( FALSE );
2372 }
2373
2374 Assert( *puiOpeningTime < *puiClosingTime );
2375
2376 return( TRUE );
2377 }
2378
2379
2380
CalculateOvernightRepairDelay(ArmsDealerID const ubArmsDealer,UINT32 uiTimeWhenFreeToStartIt,UINT32 uiMinutesToFix)2381 UINT32 CalculateOvernightRepairDelay(ArmsDealerID const ubArmsDealer, UINT32 uiTimeWhenFreeToStartIt, UINT32 uiMinutesToFix)
2382 {
2383 UINT32 uiOpeningTime, uiClosingTime;
2384 UINT32 uiMinutesClosedOvernight;
2385 UINT32 uiDelayInDays = 0;
2386 UINT32 uiDoneToday;
2387
2388
2389 Assert( uiMinutesToFix > 0 );
2390
2391 // convert world time into 24hr military time for the day he's gonna start on it
2392 uiTimeWhenFreeToStartIt = uiTimeWhenFreeToStartIt % NUM_MIN_IN_DAY;
2393
2394 if (!GetArmsDealerShopHours(ubArmsDealer, &uiOpeningTime, &uiClosingTime))
2395 {
2396 return( 0 );
2397 }
2398
2399 // if it won't get done by the end of a day
2400 while ( ( uiTimeWhenFreeToStartIt + uiMinutesToFix ) > uiClosingTime )
2401 {
2402 // this is to handle existing saves with overnight repairs
2403 if ( uiTimeWhenFreeToStartIt < uiClosingTime )
2404 {
2405 // he gets this much done before closing
2406 uiDoneToday = uiClosingTime - uiTimeWhenFreeToStartIt;
2407 // subtract how much he got done
2408 uiMinutesToFix -= uiDoneToday;
2409 Assert( uiMinutesToFix > 0 );
2410 }
2411
2412 // he starts back at it first thing in the morning
2413 uiTimeWhenFreeToStartIt = uiOpeningTime;
2414 uiDelayInDays++;
2415 }
2416
2417 uiMinutesClosedOvernight = NUM_MIN_IN_DAY - ( uiClosingTime - uiOpeningTime );
2418
2419 return ( uiDelayInDays * uiMinutesClosedOvernight );
2420 }
2421
2422
CalculateMinutesClosedBetween(ArmsDealerID const ubArmsDealer,UINT32 uiStartTime,UINT32 uiEndTime)2423 UINT32 CalculateMinutesClosedBetween(ArmsDealerID const ubArmsDealer, UINT32 uiStartTime, UINT32 uiEndTime)
2424 {
2425 UINT32 uiOpeningTime, uiClosingTime;
2426 UINT32 uiMinutesClosedOvernight;
2427 UINT32 uiDaysDifference = 0;
2428 UINT32 uiMinutesClosed = 0;
2429
2430 Assert( uiStartTime <= uiEndTime );
2431
2432 if (!GetArmsDealerShopHours(ubArmsDealer, &uiOpeningTime, &uiClosingTime))
2433 {
2434 return( 0 );
2435 }
2436
2437 uiMinutesClosedOvernight = NUM_MIN_IN_DAY - ( uiClosingTime - uiOpeningTime );
2438
2439 // NOTE: this assumes stored are only closed overnight, so all we have to do is compare the day portion
2440 uiDaysDifference = ( uiEndTime / NUM_MIN_IN_DAY ) - ( uiStartTime / NUM_MIN_IN_DAY );
2441
2442 if ( uiDaysDifference >= 2 )
2443 {
2444 // close for 1 less than that many full nights...
2445 uiMinutesClosed = ( uiDaysDifference - 1 ) * uiMinutesClosedOvernight;
2446 }
2447
2448
2449 // add partial day's closing
2450
2451 // convert start and end times into 24hr military time
2452 uiStartTime = uiStartTime % NUM_MIN_IN_DAY;
2453 uiEndTime = uiEndTime % NUM_MIN_IN_DAY;
2454
2455 // treat end time of midnight as 24:00 hours to prevent indefinite recursion and make formulas work
2456 if ( uiEndTime == 0 )
2457 {
2458 uiEndTime = NUM_MIN_IN_DAY;
2459 }
2460
2461
2462 if ( uiStartTime == uiEndTime )
2463 {
2464 if ( uiDaysDifference == 0 )
2465 {
2466 return( 0 );
2467 }
2468 else
2469 {
2470 uiMinutesClosed += uiMinutesClosedOvernight;
2471 }
2472 }
2473 if ( uiStartTime < uiEndTime )
2474 {
2475 if ( uiStartTime < uiOpeningTime )
2476 {
2477 // add how many minutes in the time range BEFORE the store opened that day
2478 uiMinutesClosed += ( MIN( uiOpeningTime, uiEndTime ) - uiStartTime );
2479 }
2480
2481 if ( uiEndTime > uiClosingTime )
2482 {
2483 // add how many minutes in the time range AFTER the store closed that day
2484 uiMinutesClosed += ( uiEndTime - MAX( uiClosingTime, uiStartTime ) );
2485 }
2486 }
2487 else
2488 {
2489 Assert( uiEndTime < uiStartTime );
2490
2491 // recursive calls! Add two separate times: before midnight, and after midnight
2492 uiMinutesClosed += CalculateMinutesClosedBetween( ubArmsDealer, uiStartTime, NUM_MIN_IN_DAY );
2493 uiMinutesClosed += CalculateMinutesClosedBetween( ubArmsDealer, 0, uiEndTime );
2494 }
2495
2496 return ( uiMinutesClosed );
2497 }
2498
2499
2500 #ifdef WITH_UNITTESTS
2501 #include "gtest/gtest.h"
2502
TEST(ArmsDealerInit,asserts)2503 TEST(ArmsDealerInit, asserts)
2504 {
2505 EXPECT_EQ(sizeof(ARMS_DEALER_STATUS), 20u);
2506 EXPECT_EQ(sizeof(SPECIAL_ITEM_INFO), 16u);
2507 EXPECT_EQ(sizeof(DEALER_SPECIAL_ITEM), 28u);
2508 }
2509
2510 #endif
2511