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