1 #include "Directories.h"
2 #include "Font.h"
3 #include "GameLoop.h"
4 #include "HImage.h"
5 #include "Handle_Items.h"
6 #include "Isometric_Utils.h"
7 #include "JAScreens.h"
8 #include "Local.h"
9 #include "Timer_Control.h"
10 #include "Types.h"
11 #include "MercPortrait.h"
12 #include "ShopKeeper_Interface.h"
13 #include "Game_Clock.h"
14 #include "Render_Dirty.h"
15 #include "VObject.h"
16 #include "VSurface.h"
17 #include "Font_Control.h"
18 #include "WordWrap.h"
19 #include "Text_Input.h"
20 #include "Interface.h"
21 #include "Interface_Control.h"
22 #include "Overhead.h"
23 #include "Cursors.h"
24 #include "SysUtil.h"
25 #include "Interface_Panels.h"
26 #include "Radar_Screen.h"
27 #include "Interface_Items.h"
28 #include "Interface_Utils.h"
29 #include "VObject_Blitters.h"
30 #include "Finances.h"
31 #include "Text.h"
32 #include "Cursor_Control.h"
33 #include "Input.h"
34 #include "Arms_Dealer_Init.h"
35 #include "English.h"
36 #include "Soldier_Add.h"
37 #include "Faces.h"
38 #include "Dialogue_Control.h"
39 #include "ShopKeeper_Quotes.h"
40 #include "GameSettings.h"
41 #include "MercTextBox.h"
42 #include "Random.h"
43 #include "Squads.h"
44 #include "Soldier_Profile.h"
45 #include "Message.h"
46 #include "LaptopSave.h"
47 #include "Quests.h"
48 #include "Weapons.h"
49 #include "Line.h"
50 #include "Drugs_And_Alcohol.h"
51 #include "Map_Screen_Interface.h"
52 #include "Soldier_Macros.h"
53 #include "ArmsDealerInvInit.h"
54 #include "OppList.h"
55 #include "NPC.h"
56 #include "LOS.h"
57 #include "Button_System.h"
58 #include "Video.h"
59 #include "MemMan.h"
60 #include "Debug.h"
61 #include "Items.h"
62 #include "UILayout.h"
63 #include "CalibreModel.h"
64 #include "ContentManager.h"
65 #include "DealerModel.h"
66 #include "GameInstance.h"
67 #include "MagazineModel.h"
68 #include "WeaponModels.h"
69 #include "policy/GamePolicy.h"
70 
71 #include <string_theory/format>
72 #include <string_theory/string>
73 
74 #include <algorithm>
75 
76 #define SKI_BUTTON_FONT				MILITARYFONT1//FONT14ARIAL
77 #define SKI_BUTTON_COLOR				73
78 
79 #define SKI_TITLE_FONT					MILITARYFONT1//FONT14ARIAL
80 #define SKI_TITLE_COLOR				169//FONT_MCOLOR_LTYELLOW
81 
82 #define SKI_LABEL_FONT					MILITARYFONT1
83 
84 #define SKI_ITEM_DESC_FONT				SMALLCOMPFONT
85 #define SKI_ITEM_PRICE_COLOR				FONT_MCOLOR_WHITE
86 
87 #define SKIT_NUMBER_FONT				BLOCKFONT2
88 
89 #define SKI_MAIN_BACKGROUND_X				0
90 #define SKI_MAIN_BACKGROUND_Y				0
91 
92 #define SKI_FACE_X					13
93 #define SKI_FACE_Y					13
94 #define SKI_FACE_WIDTH					90
95 #define SKI_FACE_HEIGHT				100
96 
97 #define SKI_PAGE_UP_ARROWS_X				121
98 #define SKI_PAGE_UP_ARROWS_Y				35
99 
100 #define SKI_PAGE_DOWN_ARROWS_X				SKI_PAGE_UP_ARROWS_X
101 #define SKI_PAGE_DOWN_ARROWS_Y				102
102 
103 #define SKI_TRANSACTION_BUTTON_X			147//214
104 #define SKI_DONE_BUTTON_X				292//414
105 #define SKI_BUTTON_Y					233
106 
107 #define SKI_MAIN_TITLE_X				112
108 #define SKI_MAIN_TITLE_Y				12
109 
110 #define SKI_MAIN_TITLE_WIDTH				420
111 
112 #define SKI_TOTAL_COST_X				9
113 #define SKI_TOTAL_COST_Y				162//159
114 #define SKI_TOTAL_COST_WIDTH				73
115 
116 #define SKI_TOTAL_VALUE_X				SKI_TOTAL_COST_X
117 #define SKI_TOTAL_VALUE_Y				291//268
118 #define SKI_TOTAL_VALUE_WIDTH				SKI_TOTAL_COST_WIDTH
119 
120 #define SKI_PLAYERS_CURRENT_BALANCE_X			SKI_TOTAL_COST_X
121 #define SKI_PLAYERS_CURRENT_BALANCE_Y			235
122 #define SKI_PLAYERS_CURRENT_BALANCE_WIDTH		SKI_TOTAL_COST_WIDTH
123 #define SKI_PLAYERS_CURRENT_BALANCE_OFFSET_TO_VALUE	265
124 
125 #define SKI_PAGE_X					112
126 #define SKI_PAGE_Y					70
127 #define SKI_PAGE_WIDTH					45
128 #define SKI_PAGE_HEIGHT				27
129 
130 
131 //Number of Inventory slots
132 #define SKI_NUM_ARMS_DEALERS_INV_SLOTS			15
133 #define SKI_NUM_ARMS_DEALERS_INV_COLS			5
134 
135 #define SKI_NUM_TRADING_INV_SLOTS			12
136 #define SKI_NUM_TRADING_INV_COLS			6
137 
138 //Inventory Slots size and offsets
139 #define SKI_INV_SLOT_WIDTH				67
140 #define SKI_INV_SLOT_HEIGHT				31
141 #define SKI_INV_HEIGHT					SKI_INV_SLOT_HEIGHT - 7
142 #define SKI_INV_WIDTH					60
143 
144 #define SKI_INV_PRICE_OFFSET_X				1
145 #define SKI_INV_PRICE_OFFSET_Y				24
146 
147 #define SKI_INV_OFFSET_X				74
148 #define SKI_INV_OFFSET_Y				36
149 
150 
151 //Start Locations for the inventory boxes
152 #define SKI_ARMS_DEALERS_INV_START_X			165
153 #define SKI_ARMS_DEALERS_INV_START_Y			30
154 
155 #define SKI_ARMS_DEALERS_TRADING_INV_X			91
156 #define SKI_ARMS_DEALERS_TRADING_INV_Y			151
157 #define SKI_ARMS_DEALERS_TRADING_INV_WIDTH		436
158 #define SKI_ARMS_DEALERS_TRADING_INV_HEIGHT		67
159 
160 
161 #define SKI_PLAYERS_TRADING_INV_X			91
162 #define SKI_PLAYERS_TRADING_INV_Y			266
163 #define SKI_PLAYERS_TRADING_INV_HEIGHT			70
164 #define SKI_PLAYERS_TRADING_INV_WIDTH			440
165 
166 #define SKI_ARMS_DEALER_TOTAL_COST_X			16
167 #define SKI_ARMS_DEALER_TOTAL_COST_Y			194//191
168 #define SKI_ARMS_DEALER_TOTAL_COST_WIDTH		59
169 #define SKI_ARMS_DEALER_TOTAL_COST_HEIGHT		20
170 
171 #define SKI_PLAYERS_TOTAL_VALUE_X			16
172 #define SKI_PLAYERS_TOTAL_VALUE_Y			310//308
173 #define SKI_PLAYERS_TOTAL_VALUE_WIDTH			59
174 #define SKI_PLAYERS_TOTAL_VALUE_HEIGHT			20
175 
176 
177 #define SKI_TACTICAL_BACKGROUND_START_X		536
178 #define SKI_TACTICAL_BACKGROUND_START_Y		0
179 #define SKI_DROP_ITEM_TO_GROUND_START_X		SKI_TACTICAL_BACKGROUND_START_X
180 #define SKI_DROP_ITEM_TO_GROUND_START_Y		262
181 #define SKI_DROP_ITEM_TO_GROUND_TEXT_START_Y		262
182 
183 #define SKI_TACTICAL_BACKGROUND_START_WIDTH		(UINT16)(SCREEN_WIDTH - SKI_TACTICAL_BACKGROUND_START_X)
184 #define SKI_TACTICAL_BACKGROUND_START_HEIGHT		340
185 
186 #define SKI_ITEM_MOVEMENT_AREA_X			85
187 #define SKI_ITEM_MOVEMENT_AREA_Y			263
188 #define SKI_ITEM_MOVEMENT_AREA_WIDTH			(SCREEN_WIDTH - SKI_ITEM_MOVEMENT_AREA_X)
189 //#define SKI_ITEM_MOVEMENT_AREA_WIDTH			448
190 #define SKI_ITEM_MOVEMENT_AREA_HEIGHT			215//72
191 
192 #define SKI_DEALER_OFFER_AREA_Y			148
193 //#define SKI_DEALER_OFFER_AREA_Y			148
194 
195 
196 #define SKI_ITEM_NUMBER_TEXT_OFFSET_X			50
197 #define SKI_ITEM_NUMBER_TEXT_OFFSET_Y			15
198 #define SKI_ITEM_NUMBER_TEXT_WIDTH			15
199 
200 #define SKI_SUBTITLE_TEXT_SIZE				512
201 
202 #define SKI_POSITION_SUBTITLES_Y			140//100
203 
204 #define SKI_SMALL_FACE_WIDTH				16
205 #define SKI_SMALL_FACE_HEIGHT				14
206 #define SKI_SMALL_FACE_OFFSET_X			52
207 
208 #define SKI_ATTACHMENT_SYMBOL_X_OFFSET			56
209 #define SKI_ATTACHMENT_SYMBOL_Y_OFFSET			14
210 
211 
212 #define SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME	4
213 
214 #define SKI_DEALERS_RANDOM_QUOTE_DELAY			15000
215 #define SKI_DEALERS_RANDOM_QUOTE_DELAY_INCREASE_RATE	5000
216 
217 #define DELAY_FOR_SHOPKEEPER_IDLE_QUOTE		20000
218 #define CHANCE_FOR_SHOPKEEPER_IDLE_QUOTE		40
219 
220 #define MAX_SUBOBJECTS_PER_OBJECT			MAX(MAX_OBJECTS_PER_SLOT, (2 + MAX_ATTACHMENTS)) // (2nd part is main item, ammo/payload, and 4 attachments)
221 
222 #define REALLY_BADLY_DAMAGED_THRESHOLD			30
223 
224 #define REPAIR_DELAY_IN_HOURS				6
225 
226 #define FLO_DISCOUNT_PERCENTAGE			10
227 
228 
229 static SGPVObject* guiMainTradeScreenImage;
230 static SGPVSurface* guiCornerWhereTacticalIsStillSeenImage; // This image is for where the corner of tactical is still seen through the shop keeper interface
231 
232 static BOOLEAN gfSKIScreenEntry = TRUE;
233 static BOOLEAN gfSKIScreenExit  = FALSE;
234 static BOOLEAN gfUserHasRequestedToLeave = FALSE;
235 
236 static BOOLEAN gfRenderScreenOnNextLoop = FALSE;
237 
238 UINT8 gubSkiDirtyLevel = SKI_DIRTY_LEVEL0;
239 
240 static ArmsDealerID gbSelectedArmsDealerID = ARMS_DEALER_INVALID; //Contains the enum value for the currently selected arms dealer
241 
242 //the quote that is in progress, in certain circumstances, we don't want queuing of related but different quotes
243 static INT32 giShopKeepDialogueEventinProgress = -1;
244 
245 INVENTORY_IN_SLOT gMoveingItem;
246 
247 const OBJECTTYPE* gpHighLightedItemObject = NULL;
248 
249 static BOOLEAN gfResetShopKeepIdleQuote = FALSE;
250 static BOOLEAN gfDoEvaluationAfterOpening = FALSE;
251 
252 struct SELECTED_ARMS_DEALERS_STATS
253 {
254 	UINT32 uiNumDistinctInventoryItems;
255 	UINT8  ubCurrentPage;
256 	UINT8  ubNumberOfPages;
257 
258 	UINT8  ubFirstItemIndexOnPage;
259 };
260 
261 
262 
263 static SELECTED_ARMS_DEALERS_STATS gSelectArmsDealerInfo;
264 
265 
266 //This pointer is used to store the inventory the arms dealer has for sale
267 static INVENTORY_IN_SLOT* gpTempDealersInventory = NULL;
268 
269 static INVENTORY_IN_SLOT ArmsDealerOfferArea[SKI_NUM_TRADING_INV_SLOTS];
270 static INVENTORY_IN_SLOT PlayersOfferArea[SKI_NUM_TRADING_INV_SLOTS];
271 
272 static OBJECTTYPE gSubObject[MAX_SUBOBJECTS_PER_OBJECT];
273 
274 static BOOLEAN gfDealerHasSaidTheEvaluateQuoteOnceThisSession = FALSE;
275 static BOOLEAN gfAlreadySaidTooMuchToRepair                   = FALSE;
276 static UINT32  guiRandomQuoteDelayTime                        = SKI_DEALERS_RANDOM_QUOTE_DELAY;
277 
278 //Index for the shopkeepers face
279 static FACETYPE* giShopKeeperFaceIndex;
280 
281 //Id for the popup box
282 static MercPopUpBox* g_popup_box;
283 
284 static BOOLEAN gfIsTheShopKeeperTalking;
285 
286 
287 static BOOLEAN gfRemindedPlayerToPickUpHisStuff = FALSE;
288 
289 static BOOLEAN gfDoneBusinessThisSession = FALSE;
290 
291 // this is used within SKI exclusively, to handle small faces
292 static UINT8  gubArrayOfEmployedMercs[MAX_CHARACTER_COUNT];
293 static SGPVObject* guiSmallSoldiersFace[MAX_CHARACTER_COUNT];
294 static UINT8  gubNumberMercsInArray;
295 
296 static UINT16 gusPositionOfSubTitlesX = 0;
297 
298 static BOOLEAN gfExitSKIDueToMessageBox = FALSE;
299 
300 OBJECTTYPE *pShopKeeperItemDescObject=NULL;
301 
302 static UINT32 guiNextFreeInvSlot;
303 
304 static BOOLEAN gfStartWithRepairsDelayedQuote = FALSE;
305 
306 static BOOLEAN gfPerformTransactionInProgress = FALSE;
307 
308 static BOOLEAN gfCommonQuoteUsedThisSession[NUM_COMMON_SK_QUOTES];
309 
310 
311 // Enums for possible evaluation results
312 enum
313 {
314 	EVAL_RESULT_NORMAL,
315 	EVAL_RESULT_OK_BUT_REALLY_DAMAGED,
316 	EVAL_RESULT_DONT_HANDLE,
317 	EVAL_RESULT_WORTHLESS,
318 	EVAL_RESULT_NOT_DAMAGED,
319 	EVAL_RESULT_NON_REPAIRABLE,
320 	EVAL_RESULT_ROCKET_RIFLE,
321 
322 	NUM_EVAL_RESULTS
323 };
324 
325 static BOOLEAN gfEvalResultQuoteSaid[NUM_EVAL_RESULTS];
326 
327 static UINT32 guiLastTimeDealerSaidNormalEvaluationQuote = 0;
328 
329 static BOOLEAN gfSkiDisplayDropItemToGroundText = FALSE;
330 
331 struct ITEM_TO_ADD_AFTER_SKI_OPEN
332 {
333 	BOOLEAN fActive;
334 	OBJECTTYPE ItemObject;
335 	INT8 bPreviousInvPos;
336 };
337 static ITEM_TO_ADD_AFTER_SKI_OPEN gItemToAdd;
338 
339 
340 //Page up buttons for the merchants
341 static void BtnSKI_InvPageUpButtonCallback(GUI_BUTTON* btn, INT32 reason);
342 static BUTTON_PICS* guiSKI_InvPageUpButtonImage;
343 static GUIButtonRef guiSKI_InvPageUpButton;
344 
345 //Page down buttons for the merchants
346 static void BtnSKI_InvPageDownButtonCallback(GUI_BUTTON* btn, INT32 reason);
347 static BUTTON_PICS* guiSKI_InvPageDownButtonImage;
348 static GUIButtonRef guiSKI_InvPageDownButton;
349 
350 
351 //Transaction buttons
352 static void BtnSKI_TransactionButtonCallback(GUI_BUTTON* btn, INT32 reason);
353 static BUTTON_PICS* guiSKI_TransactionButtonImage;
354 static GUIButtonRef guiSKI_TransactionButton;
355 
356 //Done buttons
357 static void BtnSKI_DoneButtonCallback(GUI_BUTTON* btn, INT32 reason);
358 static BUTTON_PICS* guiSKI_DoneButtonImage;
359 static GUIButtonRef guiSKI_DoneButton;
360 
361 static SGPVObject* guiItemCrossOut;
362 
363 static BOOLEAN gfDisplayNoRoomMsg = FALSE;
364 
365 //Blanket the entire screen
366 static MOUSE_REGION gSKI_EntireScreenMouseRegions;
367 
368 static MOUSE_REGION g_dealer_inventory_scroll_region;
369 
370 static MOUSE_REGION gDealersInventoryMouseRegions[SKI_NUM_ARMS_DEALERS_INV_SLOTS];
371 static MOUSE_REGION gRepairmanInventorySmallFaceMouseRegions[SKI_NUM_ARMS_DEALERS_INV_SLOTS];
372 
373 static MOUSE_REGION gDealersOfferSlotsMouseRegions[SKI_NUM_TRADING_INV_SLOTS];
374 
375 static MOUSE_REGION gPlayersOfferSlotsMouseRegions[SKI_NUM_TRADING_INV_SLOTS];
376 
377 static MOUSE_REGION gDealersOfferSlotsSmallFaceMouseRegions[SKI_NUM_TRADING_INV_SLOTS];
378 static MOUSE_REGION gPlayersOfferSlotsSmallFaceMouseRegions[SKI_NUM_TRADING_INV_SLOTS];
379 
380 
381 static MOUSE_REGION gSkiInventoryMovementAreaMouseRegions;
382 
383 
384 //Mouse region for the subtitles region when the merc is talking
385 static MOUSE_REGION gShopKeeperSubTitleMouseRegion;
386 
387 static MOUSE_REGION gArmsDealersFaceMouseRegions;
388 
389 
390 //Region to allow the user to drop items to the ground
391 static MOUSE_REGION gArmsDealersDropItemToGroundMouseRegions;
392 
393 //
394 // screen handler functions
395 //
396 
ShopKeeperScreenInit()397 void ShopKeeperScreenInit()
398 {
399 	//Set so next time we come in, we can set up
400 	gfSKIScreenEntry = TRUE;
401 }
402 
403 
404 static void EnterShopKeeperInterface(void);
405 static void ExitShopKeeperInterface(void);
406 static void GetShopKeeperInterfaceUserInput(void);
407 static void HandleShopKeeperInterface(void);
408 static void RenderShopKeeperInterface(void);
409 
410 
SelectedArmsDealer()411 const DealerModel* SelectedArmsDealer()
412 {
413 	auto dealer = GCM->getDealer(gbSelectedArmsDealerID);
414 	if (dealer == NULL)
415 	{
416 		ST::string err = ST::format("Dealer is NULL ({})", gbSelectedArmsDealerID);
417 		throw std::runtime_error(err.to_std_string());
418 	}
419 	return dealer;
420 }
421 
ShopKeeperScreenHandle()422 ScreenID ShopKeeperScreenHandle()
423 {
424 	if( gfSKIScreenEntry )
425 	{
426 		PauseGame();
427 
428 		try
429 		{
430 			EnterShopKeeperInterface();
431 		}
432 		catch (...) // XXX fishy, should probably propagate
433 		{
434 			gfSKIScreenExit = TRUE;
435 			EnterTacticalScreen();
436 			return SHOPKEEPER_SCREEN;
437 		}
438 
439 		gfSKIScreenEntry = FALSE;
440 		gfSKIScreenExit  = FALSE;
441 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
442 		gfRenderScreenOnNextLoop = TRUE;
443 		InvalidateScreen();
444 	}
445 
446 	if( gfRenderScreenOnNextLoop )
447 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
448 
449 	RestoreBackgroundRects();
450 
451 	GetShopKeeperInterfaceUserInput();
452 
453 
454 	// Check for any newly added items...
455 	if ( gpSMCurrentMerc->fCheckForNewlyAddedItems )
456 	{
457 		// Startup any newly added items....
458 		CheckForAnyNewlyAddedItems( gpSMCurrentMerc );
459 		gpSMCurrentMerc->fCheckForNewlyAddedItems = FALSE;
460 	}
461 
462 
463 	HandleShopKeeperInterface();
464 
465 
466 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
467 	{
468 		RenderShopKeeperInterface();
469 
470 		fInterfacePanelDirty = DIRTYLEVEL2;
471 
472 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL0;
473 	}
474 	else if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL1 )
475 	{
476 		fInterfacePanelDirty = DIRTYLEVEL2;
477 
478 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL0;
479 	}
480 
481 	// render buttons marked dirty
482 	ShopKeeperInterface_SetSMpanelButtonsState(false);
483 	RenderButtons( );
484 
485 	// render help
486 	SaveBackgroundRects( );
487 	RenderButtonsFastHelp( );
488 
489 	ExecuteBaseDirtyRectQueue();
490 	EndFrameBufferRender();
491 
492 	if( gfSKIScreenExit )
493 	{
494 		ExitShopKeeperInterface();
495 		gfSKIScreenExit = FALSE;
496 		gfSKIScreenEntry = TRUE;
497 		EnterTacticalScreen( );
498 		UnPauseGame();
499 	}
500 
501 	if ( gfDisplayNoRoomMsg )
502 	{
503 		// tell player there's not enough room in the player's offer area
504 		// ARM: message is delayed because we need the mouse restriction to be in place
505 		// BEFORE it comes up so it gets lifted/restored
506 		DoSkiMessageBox(SKI_Text[SKI_TEXT_NO_MORE_ROOM_IN_PLAYER_OFFER_AREA], SHOPKEEPER_SCREEN,
507 					MSG_BOX_FLAG_OK, NULL);
508 
509 		gfDisplayNoRoomMsg = FALSE;
510 	}
511 
512 
513 	return( SHOPKEEPER_SCREEN );
514 }
515 
516 
ShopKeeperScreenShutdown(void)517 void ShopKeeperScreenShutdown(void)
518 {
519 	ShutDownArmsDealers();
520 }
521 
522 
MakeButton(BUTTON_PICS * img,const ST::string & text,INT16 x,INT16 prio,GUI_CALLBACK click,const ST::string & help)523 static GUIButtonRef MakeButton(BUTTON_PICS* img, const ST::string& text, INT16 x, INT16 prio, GUI_CALLBACK click, const ST::string& help)
524 {
525 	const INT16 text_col   = SKI_BUTTON_COLOR;
526 	const INT16 shadow_col = DEFAULT_SHADOW;
527 	GUIButtonRef const btn = CreateIconAndTextButton(img, text, SKI_BUTTON_FONT, text_col, shadow_col, text_col, shadow_col, x, SKI_BUTTON_Y, prio, click);
528 	btn->SpecifyDisabledStyle(GUI_BUTTON::DISABLED_STYLE_HATCHED);
529 	btn->SetFastHelpText(help);
530 	return btn;
531 }
532 
533 
534 static UINT8 CountNumberOfItemsInThePlayersOfferArea();
535 static void CreateSkiInventorySlotMouseRegions(void);
536 static void HandlePossibleRepairDelays(void);
537 static void HandleShopKeeperDialog(UINT8 ubInit);
538 static BOOLEAN InitShopKeepersFace(UINT8 ubMercID);
539 static void InitializeShopKeeper(BOOLEAN fResetPage);
540 static BOOLEAN OfferObjectToDealer(OBJECTTYPE* pComplexObject, UINT8 ubOwnerProfileId, INT8 bOwnerSlotId);
541 static void ResetAllQuoteSaidFlags();
542 static void SelectArmsDealersDropItemToGroundMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
543 static void SelectArmsDealersDropItemToGroundRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
544 static void SelectArmsDealersFaceRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
545 
546 
EnterShopKeeperInterface(void)547 static void EnterShopKeeperInterface(void)
548 {
549 	// make sure current merc is close enough and eligible to talk to the shopkeeper.
550 	AssertMsg(CanMercInteractWithSelectedShopkeeper(GetSelectedMan()), "Selected merc can't interact with shopkeeper.  Send save AM-1");
551 
552 	// Create a video surface to blt corner of the tactical screen that still shines through
553 	guiCornerWhereTacticalIsStillSeenImage = AddVideoSurface(SKI_TACTICAL_BACKGROUND_START_WIDTH, SKI_TACTICAL_BACKGROUND_START_HEIGHT, PIXEL_DEPTH);
554 
555 	//Clear out all the save background rects
556 	EmptyBackgroundRects( );
557 
558 	if( gfExitSKIDueToMessageBox )
559 	{
560 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
561 		gfExitSKIDueToMessageBox = FALSE;
562 	}
563 
564 	//Check to make sure the inventory is null ( should always be null if we are just coming in to the SKI )
565 	Assert( gpTempDealersInventory == NULL );
566 
567 	//Reinitialize the team panel to be the SM panel
568 	SetCurrentInterfacePanel( SM_PANEL );
569 	SOLDIERTYPE* const sel = GetSelectedMan();
570 	SetCurrentTacticalPanelCurrentMerc(sel);
571 	SetSMPanelCurrentMerc(sel);
572 
573 	// load the Main trade screen backgroiund image
574 	guiMainTradeScreenImage = AddVideoObjectFromFile(INTERFACEDIR "/tradescreen.sti");
575 
576 	// load the Main trade screen background image
577 	guiItemCrossOut = AddVideoObjectFromFile(INTERFACEDIR "/itemcrossout.sti");
578 
579 	//Create an array of all mercs (anywhere!) currently in the player's employ, and load their small faces
580 	// This is to support showing of repair item owner's faces even when they're not in the sector, as long as they still work for player
581 	gubNumberMercsInArray = 0;
582 	CFOR_EACH_IN_TEAM(pSoldier, OUR_TEAM)
583 	{
584 		if (pSoldier->ubProfile != NO_PROFILE && !IsMechanical(*pSoldier))
585 		{
586 			// remember whose face is in this slot
587 			gubArrayOfEmployedMercs[ gubNumberMercsInArray ] = pSoldier->ubProfile;
588 
589 			//While we are at it, add their small face
590 			guiSmallSoldiersFace[gubNumberMercsInArray] = Load33Portrait(GetProfile(pSoldier->ubProfile));
591 
592 			gubNumberMercsInArray++;
593 		}
594 	}
595 
596 	//Load the graphic for the arrow button
597 	guiSKI_InvPageUpButtonImage   = LoadButtonImage(INTERFACEDIR "/tradescrollarrows.sti", 0, 1);
598 	guiSKI_InvPageDownButtonImage = UseLoadedButtonImage(guiSKI_InvPageUpButtonImage,      2, 3);
599 
600 
601 	//Page up button for the merchant inventory
602 	guiSKI_InvPageUpButton = QuickCreateButton(guiSKI_InvPageUpButtonImage, SKI_PAGE_UP_ARROWS_X, SKI_PAGE_UP_ARROWS_Y, MSYS_PRIORITY_HIGHEST, BtnSKI_InvPageUpButtonCallback);
603 	guiSKI_InvPageUpButton->SpecifyDisabledStyle(GUI_BUTTON::DISABLED_STYLE_HATCHED);
604 
605 	//Page down button for the merchant inventory
606 	guiSKI_InvPageDownButton = QuickCreateButton(guiSKI_InvPageDownButtonImage, SKI_PAGE_DOWN_ARROWS_X, SKI_PAGE_DOWN_ARROWS_Y, MSYS_PRIORITY_HIGHEST, BtnSKI_InvPageDownButtonCallback);
607 	guiSKI_InvPageDownButton->SpecifyDisabledStyle(GUI_BUTTON::DISABLED_STYLE_HATCHED);
608 
609 
610 	guiSKI_TransactionButtonImage = LoadButtonImage(INTERFACEDIR "/tradebuttons.sti",   0, 1);
611 	guiSKI_DoneButtonImage        = UseLoadedButtonImage(guiSKI_TransactionButtonImage, 0, 1);
612 
613 	//Transaction button
614 	//if the dealer repairs, use the repair fast help text for the transaction button
615 	ST::string help;
616 	if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
617 	{
618 		help = SkiMessageBoxText[SKI_REPAIR_TRANSACTION_BUTTON_HELP_TEXT];
619 	}
620 	else
621 	{
622 		help = SkiMessageBoxText[SKI_TRANSACTION_BUTTON_HELP_TEXT];
623 	}
624 	guiSKI_TransactionButton = MakeButton(guiSKI_TransactionButtonImage, SKI_Text[SKI_TEXT_TRANSACTION], SKI_TRANSACTION_BUTTON_X, MSYS_PRIORITY_HIGH, BtnSKI_TransactionButtonCallback, help);
625 
626 	//Done button
627 	guiSKI_DoneButton = MakeButton(guiSKI_DoneButtonImage, SKI_Text[SKI_TEXT_DONE], SKI_DONE_BUTTON_X,  MSYS_PRIORITY_HIGH + 10, BtnSKI_DoneButtonCallback, SkiMessageBoxText[SKI_DONE_BUTTON_HELP_TEXT]);
628 
629 	//Blanket the entire screen
630 	MSYS_DefineRegion(&gSKI_EntireScreenMouseRegions, 0, 0, SCREEN_WIDTH, 339, MSYS_PRIORITY_HIGH - 2, CURSOR_NORMAL, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK);
631 
632 	//Create the mouse regions for the inventory slot
633 	CreateSkiInventorySlotMouseRegions( );
634 
635 
636 
637 	//Create the mouse region to limit the movement of the item cursos
638 	MSYS_DefineRegion( &gSkiInventoryMovementAreaMouseRegions, SKI_ITEM_MOVEMENT_AREA_X, SKI_ITEM_MOVEMENT_AREA_Y, (UINT16)(SKI_ITEM_MOVEMENT_AREA_X+SKI_ITEM_MOVEMENT_AREA_WIDTH), (UINT16)(SKI_ITEM_MOVEMENT_AREA_Y+SKI_ITEM_MOVEMENT_AREA_HEIGHT), MSYS_PRIORITY_HIGH-1,
639 				CURSOR_NORMAL, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK );
640 
641 	//Disable the region that limits the movement of the cursor with the item
642 	gSkiInventoryMovementAreaMouseRegions.Disable();
643 
644 
645 	//Create the mouse region for the shopkeeper's face
646 	MSYS_DefineRegion(&gArmsDealersFaceMouseRegions, SKI_FACE_X, SKI_FACE_Y,
647 				(UINT16)(SKI_FACE_X+SKI_FACE_WIDTH), (UINT16)(SKI_FACE_Y+SKI_FACE_HEIGHT),
648 				MSYS_PRIORITY_HIGH-1,
649 				CURSOR_NORMAL, MSYS_NO_CALLBACK, SelectArmsDealersFaceRegionCallBack);
650 
651 	std::fill_n(ArmsDealerOfferArea, SKI_NUM_TRADING_INV_SLOTS, INVENTORY_IN_SLOT{});
652 	std::fill_n(PlayersOfferArea, SKI_NUM_TRADING_INV_SLOTS, INVENTORY_IN_SLOT{});
653 
654 
655 	if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
656 	{
657 		HandlePossibleRepairDelays();
658 	}
659 
660 
661 	//Setup the currently selected arms dealer
662 	InitializeShopKeeper( TRUE );
663 
664 	gMoveingItem = INVENTORY_IN_SLOT{};
665 
666 	std::fill(std::begin(gfCommonQuoteUsedThisSession), std::end(gfCommonQuoteUsedThisSession), FALSE);
667 
668 	//Init the shopkeepers face
669 	InitShopKeepersFace( SelectedArmsDealer()->profileID );
670 
671 	gfDoneBusinessThisSession = FALSE;
672 
673 	//Call this to set the fact that we just entered the screen
674 	HandleShopKeeperDialog( 1 );
675 
676 	ResetAllQuoteSaidFlags();
677 
678 	//Reset the highlighted item pointer;
679 	gpHighLightedItemObject = NULL;
680 
681 	//Reset
682 	gfRemindedPlayerToPickUpHisStuff = FALSE;
683 	gfUserHasRequestedToLeave = FALSE;
684 	gfDisplayNoRoomMsg = FALSE;
685 
686 	//Disable the map radar region
687 	gRadarRegion.Disable();
688 
689 	gfDoEvaluationAfterOpening = FALSE;
690 
691 	if( gItemToAdd.fActive )
692 	{
693 		BOOLEAN fAddedOK = FALSE;
694 		INT8 bSlotNum = gItemToAdd.bPreviousInvPos;
695 
696 		//if this is NOT a repair dealer or he is is but there is enough space in the player's offer area
697 		// (you can't be out of space if it isn't a repairman, only they can fill it up with repaired items!)
698 		if( (!DoesDealerDoRepairs(gbSelectedArmsDealerID)) ||
699 			( CountNumberOfItemsInThePlayersOfferArea( ) < SKI_NUM_ARMS_DEALERS_INV_SLOTS ) )
700 		{
701 			// if we're supposed to store the original pocket #, but that pocket still holds more of these
702 			if ( ( bSlotNum != -1 ) && ( gpSMCurrentMerc->inv[ bSlotNum ].ubNumberOfObjects > 0 ) )
703 			{
704 				// then we can't store the pocket #, because our system can't return stacked objects
705 				bSlotNum = -1;
706 			}
707 
708 			if ( OfferObjectToDealer( &(gItemToAdd.ItemObject), gpSMCurrentMerc->ubProfile, bSlotNum ) )
709 			{
710 				fAddedOK = TRUE;
711 			}
712 		}
713 
714 		if ( fAddedOK )
715 		{
716 			// evaluate it
717 			gfDoEvaluationAfterOpening = TRUE;
718 		}
719 		else
720 		{
721 			//add the item back to the current PC into the slot it came from
722 			gpSMCurrentMerc->inv[gItemToAdd.bPreviousInvPos] = gItemToAdd.ItemObject;
723 		}
724 
725 		//Clear the contents of the structure
726 		gItemToAdd = ITEM_TO_ADD_AFTER_SKI_OPEN{};
727 		gItemToAdd.fActive = FALSE;
728 	}
729 
730 	// Dirty the bottom panel
731 	fInterfacePanelDirty = DIRTYLEVEL2;
732 
733 
734 	gfDealerHasSaidTheEvaluateQuoteOnceThisSession = FALSE;
735 	guiRandomQuoteDelayTime = SKI_DEALERS_RANDOM_QUOTE_DELAY;
736 
737 	pShopKeeperItemDescObject = NULL;
738 
739 
740 	//Region to allow the user to drop items to the ground
741 	MSYS_DefineRegion(&gArmsDealersDropItemToGroundMouseRegions, SKI_DROP_ITEM_TO_GROUND_START_X,
742 				SKI_DROP_ITEM_TO_GROUND_START_Y, SCREEN_WIDTH, 339, MSYS_PRIORITY_HIGH,
743 				CURSOR_NORMAL, SelectArmsDealersDropItemToGroundMovementRegionCallBack,
744 				SelectArmsDealersDropItemToGroundRegionCallBack);
745 	//			CURSOR_NORMAL, MSYS_NO_CALLBACK, SelectArmsDealersDropItemToGroundRegionCallBack );
746 
747 	gfSkiDisplayDropItemToGroundText = FALSE;
748 
749 	// by default re-enable calls to PerformTransaction()
750 	gfPerformTransactionInProgress = FALSE;
751 }
752 
753 
InitShopKeepersFace(UINT8 ubMercID)754 static BOOLEAN InitShopKeepersFace(UINT8 ubMercID)
755 {
756 	SOLDIERTYPE* const pSoldier = FindSoldierByProfileID(SelectedArmsDealer()->profileID);
757 	FACETYPE& f = InitFace(ubMercID, pSoldier, FACE_BIGFACE);
758 	giShopKeeperFaceIndex = &f;
759 
760 	SetAutoFaceActive(FRAME_BUFFER, FACE_AUTO_RESTORE_BUFFER, f, SKI_FACE_X, SKI_FACE_Y);
761 
762 	//Set it so the face cannot be set InActive
763 	f.uiFlags |= FACE_INACTIVE_HANDLED_ELSEWHERE;
764 
765 	RenderAutoFace(f);
766 
767 	return(TRUE);
768 }
769 
770 
771 static void DestroySkiInventorySlotMouseRegions(void);
772 static void RemoveShopKeeperSubTitledText(void);
773 static void ShutUpShopKeeper(void);
774 
775 
ExitShopKeeperInterface(void)776 static void ExitShopKeeperInterface(void)
777 {
778 	UINT8 ubCnt;
779 
780 	if( gfExitSKIDueToMessageBox )
781 	{
782 		gfSKIScreenExit = FALSE;
783 
784 		//gfExitSKIDueToMessageBox = FALSE;
785 	}
786 
787 	// ItemDescriptionBox should be cleared in either case
788 	if( InItemDescriptionBox( ) || pShopKeeperItemDescObject != NULL )
789 	{
790 		DeleteItemDescriptionBox( );
791 	}
792 
793 	FreeMouseCursor();
794 
795 	//Delete the main shopkeep background
796 	DeleteVideoObject(guiMainTradeScreenImage);
797 	DeleteVideoObject(guiItemCrossOut);
798 	DeleteVideoSurface(guiCornerWhereTacticalIsStillSeenImage);
799 
800 	ShutUpShopKeeper();
801 
802 	UnloadButtonImage( guiSKI_InvPageUpButtonImage );
803 	UnloadButtonImage( guiSKI_InvPageDownButtonImage );
804 
805 	UnloadButtonImage( guiSKI_TransactionButtonImage );
806 	UnloadButtonImage( guiSKI_DoneButtonImage );
807 
808 	//loop through the area and delete small faces
809 	for(ubCnt=0; ubCnt<gubNumberMercsInArray; ubCnt++)
810 	{
811 		DeleteVideoObject(guiSmallSoldiersFace[ubCnt]);
812 	}
813 
814 	RemoveButton( guiSKI_InvPageUpButton );
815 	RemoveButton( guiSKI_InvPageDownButton );
816 
817 	RemoveButton( guiSKI_TransactionButton );
818 	RemoveButton( guiSKI_DoneButton );
819 
820 	MSYS_RemoveRegion( &gSKI_EntireScreenMouseRegions);
821 
822 	MSYS_RemoveRegion( &gSkiInventoryMovementAreaMouseRegions );
823 
824 	//Remove the region for the face
825 	MSYS_RemoveRegion( &gArmsDealersFaceMouseRegions );
826 
827 	//Region to allow the user to drop items to the ground
828 	MSYS_RemoveRegion( &gArmsDealersDropItemToGroundMouseRegions );
829 
830 	//Destroy the mouse regions for the inventory slots
831 	DestroySkiInventorySlotMouseRegions( );
832 
833 	//if there is a temp inventory array, destroy it
834 	if( gpTempDealersInventory )
835 	{
836 		delete[] gpTempDealersInventory;
837 		gpTempDealersInventory = NULL;
838 	}
839 
840 	//if there is a subtitles box up, remove it
841 	RemoveShopKeeperSubTitledText();
842 
843 	//Get rid of the ShopKeeper face
844 	DeleteFace( giShopKeeperFaceIndex );
845 
846 	gRadarRegion.Enable();
847 
848 	gfSMDisableForItems = FALSE;
849 	ShopKeeperInterface_SetSMpanelButtonsState(true);
850 }
851 
852 static void DisplayArmsDealerCurrentInventoryPage(void);
853 static void DisplayArmsDealerOfferArea(void);
854 static void DisplayPlayersOfferArea(void);
855 static void DisplayTalkingArmsDealer(void);
856 static void DisplayTheSkiDropItemToGroundString(void);
857 static void EnableDisableEvaluateAndTransactionButtons(void);
858 static void EvaluateItemAddedToPlayersOfferArea(INT8 bSlotID, BOOLEAN fFirstOne);
859 static void HandleCheckIfEnoughOnTheTable(void);
860 
HandleShopKeeperInterface(void)861 static void HandleShopKeeperInterface(void)
862 {
863 	UINT8 ubStatusOfSkiRenderDirtyFlag = gubSkiDirtyLevel;
864 
865 	INT32 iCounter = 0;
866 
867 
868 	//if we are in the item desc panel, disable the buttons
869 	if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL )
870 	{
871 		ShopKeeperInterface_SetSMpanelButtonsState(false);
872 		DisableButton( guiSKI_InvPageUpButton );
873 		DisableButton( guiSKI_InvPageDownButton );
874 		DisableButton( guiSKI_TransactionButton );
875 		DisableButton( guiSKI_DoneButton );
876 
877 		//make sure the buttons dont render
878 		//guiSKI_InvPageUpButton->uiFlags   |= BUTTON_FORCE_UNDIRTY;
879 		//guiSKI_InvPageDownButton->uiFlags |= BUTTON_FORCE_UNDIRTY;
880 		guiSKI_TransactionButton->uiFlags |= BUTTON_FORCE_UNDIRTY;
881 		guiSKI_DoneButton->uiFlags        |= BUTTON_FORCE_UNDIRTY;
882 
883 		// make sure the shop keeper doesn't start talking ( reset the timing variable )
884 		HandleShopKeeperDialog( 2 );
885 
886 		return;
887 	}
888 
889 
890 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
891 	{
892 		fInterfacePanelDirty = DIRTYLEVEL2;
893 	}
894 
895 	RenderTacticalInterface( );
896 
897 	if ( InItemStackPopup( ) )
898 	{
899 		if ( fInterfacePanelDirty == DIRTYLEVEL2 )
900 		{
901 			RenderItemStackPopup( TRUE );
902 		}
903 		else
904 		{
905 			RenderItemStackPopup( FALSE );
906 		}
907 	}
908 
909 	// handle check if enough on the table
910 	HandleCheckIfEnoughOnTheTable( );
911 
912 	// Render view window
913 	fInterfacePanelDirty = DIRTYLEVEL2;
914 	RenderRadarScreen( );
915 
916 	if( fInterfacePanelDirty == DIRTYLEVEL2 )
917 	{
918 		fInterfacePanelDirty = DIRTYLEVEL0;
919 	}
920 
921 	RenderClock();
922 	RenderTownIDString( );
923 
924 	DisplayTalkingArmsDealer();
925 
926 
927 	DisplayArmsDealerCurrentInventoryPage( );
928 
929 	DisplayArmsDealerOfferArea();
930 
931 	DisplayPlayersOfferArea();
932 
933 	EnableDisableEvaluateAndTransactionButtons();
934 
935 	MarkButtonsDirty( );
936 
937 	if( gfDoEvaluationAfterOpening )
938 	{
939 		BOOLEAN fFirstOne = TRUE;
940 
941 		gfDoEvaluationAfterOpening = FALSE;
942 
943 		for( iCounter = 0; iCounter < SKI_NUM_TRADING_INV_SLOTS; iCounter++ )
944 		{
945 			if( PlayersOfferArea[ iCounter ].fActive )
946 			{
947 				EvaluateItemAddedToPlayersOfferArea( ( INT8 ) iCounter, fFirstOne );
948 				fFirstOne = FALSE;
949 			}
950 		}
951 
952 		gfAlreadySaidTooMuchToRepair = FALSE;
953 	}
954 
955 	//if the Ski dirty flag was changed to a lower value, make sure it is set properly
956 	if( ubStatusOfSkiRenderDirtyFlag > gubSkiDirtyLevel )
957 		gubSkiDirtyLevel = ubStatusOfSkiRenderDirtyFlag;
958 
959 	//if the merc is talking and there is an item currently being highlighted
960 	// ( this gets rid of the item burning through the dealers text box )
961 	if (gfIsTheShopKeeperTalking &&
962 			(gpHighLightedItemObject != NULL || gubSkiDirtyLevel != SKI_DIRTY_LEVEL0))
963 	{
964 		RenderMercPopUpBox(g_popup_box, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER);
965 	}
966 
967 	//if we are to display the drop item to ground text
968 	if( gfSkiDisplayDropItemToGroundText )
969 	{
970 		DisplayTheSkiDropItemToGroundString();
971 	}
972 }
973 
974 
975 static void CrossOutUnwantedItems(void);
976 static void RestoreTacticalBackGround(void);
977 
978 
RenderShopKeeperInterface(void)979 static void RenderShopKeeperInterface(void)
980 {
981 	if (InItemDescriptionBox() && pShopKeeperItemDescObject != NULL) return;
982 
983 	//RenderTacticalInterface( );
984 	// Render view window
985 	//RenderRadarScreen( );
986 
987 	BltVideoObject(FRAME_BUFFER, guiMainTradeScreenImage, 0, SKI_MAIN_BACKGROUND_X, SKI_MAIN_BACKGROUND_Y);
988 
989 	//Display the Title
990 	DrawTextToScreen(SKI_Text[SKI_TEXT_MERCHADISE_IN_STOCK], SKI_MAIN_TITLE_X, SKI_MAIN_TITLE_Y, SKI_MAIN_TITLE_WIDTH, SKI_TITLE_FONT, SKI_TITLE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
991 
992 	//if the dealer repairs
993 	if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
994 	{
995 		//Display the Repair cost text
996 		DisplayWrappedString(SKI_TOTAL_COST_X, SKI_TOTAL_COST_Y, SKI_TOTAL_COST_WIDTH, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_REPAIR_COST], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
997 	}
998 	else
999 	{
1000 		//Display the Total cost text
1001 		DisplayWrappedString(SKI_TOTAL_COST_X, SKI_TOTAL_COST_Y, SKI_TOTAL_COST_WIDTH, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_TOTAL_COST], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
1002 	}
1003 
1004 	//Display the total value text
1005 	DisplayWrappedString(SKI_TOTAL_VALUE_X, SKI_TOTAL_VALUE_Y, SKI_TOTAL_VALUE_WIDTH, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_TOTAL_VALUE], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
1006 
1007 
1008 	//Display the players current balance text
1009 	DisplayWrappedString(SKI_PLAYERS_CURRENT_BALANCE_X, SKI_PLAYERS_CURRENT_BALANCE_Y, SKI_PLAYERS_CURRENT_BALANCE_WIDTH, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SkiMessageBoxText[SKI_PLAYERS_CURRENT_BALANCE], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
1010 
1011 	//Display the players current balance value
1012 	DrawTextToScreen(SPrintMoney(LaptopSaveInfo.iCurrentBalance), SKI_PLAYERS_CURRENT_BALANCE_X, SKI_PLAYERS_CURRENT_BALANCE_OFFSET_TO_VALUE, SKI_PLAYERS_CURRENT_BALANCE_WIDTH, FONT10ARIAL, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED | MARK_DIRTY);
1013 
1014 	BlitBufferToBuffer(FRAME_BUFFER, guiSAVEBUFFER, 0, 0, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_HEIGHT);
1015 
1016 	//At this point the background is pure, copy it to the save buffer
1017 	if( gfRenderScreenOnNextLoop )
1018 	{
1019 	//	BlitBufferToBuffer(FRAME_BUFFER, guiCornerWhereTacticalIsStillSeenImage, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, SKI_TACTICAL_BACKGROUND_START_WIDTH, SKI_TACTICAL_BACKGROUND_START_HEIGHT);
1020 		SGPBox const SrcRect =
1021 		{
1022 			SKI_TACTICAL_BACKGROUND_START_X,
1023 			SKI_TACTICAL_BACKGROUND_START_Y,
1024 			SKI_TACTICAL_BACKGROUND_START_WIDTH,
1025 			SKI_TACTICAL_BACKGROUND_START_HEIGHT
1026 		};
1027 		BltVideoSurface(guiCornerWhereTacticalIsStillSeenImage, guiSAVEBUFFER, 0, 0, &SrcRect);
1028 
1029 		gfRenderScreenOnNextLoop = FALSE;
1030 	}
1031 
1032 	DisplayArmsDealerCurrentInventoryPage( );
1033 
1034 	DisplayArmsDealerOfferArea();
1035 
1036 	DisplayPlayersOfferArea();
1037 
1038 	//if the merc is talking and the screen has been dirtied
1039 	if( gfIsTheShopKeeperTalking )
1040 		RenderMercPopUpBox(g_popup_box, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER);
1041 
1042 	CrossOutUnwantedItems( );
1043 
1044 	RenderClock();
1045 	RenderTownIDString( );
1046 
1047 	//RenderTacticalInterface( );
1048 
1049 	//Restore the tactical background that is visble behind the SKI panel
1050 	RestoreTacticalBackGround();
1051 
1052 	InvalidateScreen();
1053 }
1054 
1055 
RestoreTacticalBackGround(void)1056 static void RestoreTacticalBackGround(void)
1057 {
1058 	//Restore the background before blitting the text back on
1059 	//RestoreExternBackgroundRect( SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, SKI_TACTICAL_BACKGROUND_START_WIDTH, SKI_TACTICAL_BACKGROUND_START_HEIGHT );
1060 
1061 	//BlitBufferToBuffer(guiCornerWhereTacticalIsStillSeenImage, FRAME_BUFFER, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, SKI_TACTICAL_BACKGROUND_START_WIDTH, SKI_TACTICAL_BACKGROUND_START_HEIGHT);
1062 
1063 	BltVideoSurface(FRAME_BUFFER, guiCornerWhereTacticalIsStillSeenImage, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, NULL);
1064 
1065 	InvalidateScreen();
1066 }
1067 
1068 
1069 static void ExitSKIRequested(void);
1070 
1071 
GetShopKeeperInterfaceUserInput(void)1072 static void GetShopKeeperInterfaceUserInput(void)
1073 {
1074 	InputAtom Event;
1075 
1076 	while( DequeueEvent( &Event ) )
1077 	{
1078 		if( !HandleTextInput( &Event ) && Event.usEvent == KEY_DOWN )
1079 		{
1080 			switch( Event.usParam )
1081 			{
1082 				case SDLK_ESCAPE:
1083 					// clean exits - does quotes, shuts up shopkeeper, etc.
1084 					ExitSKIRequested();
1085 					break;
1086 
1087 				case 'x':
1088 					if( Event.usKeyState & ALT_DOWN )
1089 					{
1090 						HandleShortCutExitState( );
1091 					}
1092 					break;
1093 
1094 				case SDLK_SPACE:
1095 					{
1096 						DeleteItemDescriptionBox( );
1097 
1098 						// skip Robot and EPCs
1099 						SOLDIERTYPE* const s = FindNextActiveAndAliveMerc(gpSMCurrentMerc, FALSE, TRUE);
1100 						gSelectSMPanelToMerc = s;
1101 						LocateSoldier(s, DONTSETLOCATOR);
1102 						// refresh background for player slots (in case item values change due to Flo's discount)
1103 						gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1104 					}
1105 					break;
1106 			}
1107 		}
1108 	}
1109 }
1110 
1111 static void EnableDisableDealersInventoryPageButtons(void);
1112 
ShopInventoryPageUp()1113 static void ShopInventoryPageUp()
1114 {
1115 	if (gSelectArmsDealerInfo.ubCurrentPage > 1)
1116 	{
1117 		--gSelectArmsDealerInfo.ubCurrentPage;
1118 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1119 		EnableDisableDealersInventoryPageButtons();
1120 	}
1121 }
1122 
1123 
ShopInventoryPageDown()1124 static void ShopInventoryPageDown()
1125 {
1126 	if (gSelectArmsDealerInfo.ubCurrentPage < gSelectArmsDealerInfo.ubNumberOfPages)
1127 	{
1128 		++gSelectArmsDealerInfo.ubCurrentPage;
1129 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1130 		EnableDisableDealersInventoryPageButtons();
1131 	}
1132 }
1133 
1134 
BtnSKI_InvPageUpButtonCallback(GUI_BUTTON * btn,INT32 reason)1135 static void BtnSKI_InvPageUpButtonCallback(GUI_BUTTON* btn, INT32 reason)
1136 {
1137 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1138 	{
1139 		ShopInventoryPageUp();
1140 	}
1141 }
1142 
1143 
BtnSKI_InvPageDownButtonCallback(GUI_BUTTON * btn,INT32 reason)1144 static void BtnSKI_InvPageDownButtonCallback(GUI_BUTTON* btn, INT32 reason)
1145 {
1146 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1147 	{
1148 		ShopInventoryPageDown();
1149 	}
1150 }
1151 
1152 
1153 static void PerformTransaction(UINT32 uiMoneyFromPlayersAccount);
1154 
1155 
BtnSKI_TransactionButtonCallback(GUI_BUTTON * btn,INT32 reason)1156 static void BtnSKI_TransactionButtonCallback(GUI_BUTTON* btn, INT32 reason)
1157 {
1158 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1159 	{
1160 		// if not already doing it (multiple clicks can be queued up while shopkeeper is still talking)
1161 		if ( !gfPerformTransactionInProgress )
1162 		{
1163 			// shut him up
1164 			ShutUpShopKeeper();
1165 
1166 			giShopKeepDialogueEventinProgress = -1;
1167 
1168 			PerformTransaction( 0 );
1169 		}
1170 	}
1171 }
1172 
1173 
BtnSKI_DoneButtonCallback(GUI_BUTTON * btn,INT32 reason)1174 static void BtnSKI_DoneButtonCallback(GUI_BUTTON* btn, INT32 reason)
1175 {
1176 	if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1177 	{
1178 		ExitSKIRequested();
1179 	}
1180 }
1181 
1182 
DealerInventoryScrollRegionCallback(MOUSE_REGION * const,INT32 const reason)1183 static void DealerInventoryScrollRegionCallback(MOUSE_REGION* const, INT32 const reason)
1184 {
1185 	if (reason & MSYS_CALLBACK_REASON_WHEEL_UP)
1186 	{
1187 		ShopInventoryPageUp();
1188 	}
1189 	else if (reason & MSYS_CALLBACK_REASON_WHEEL_DOWN)
1190 	{
1191 		ShopInventoryPageDown();
1192 	}
1193 }
1194 
1195 
1196 static void SelectDealersInventoryMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1197 static void SelectDealersInventoryRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1198 static void SelectDealersOfferSlotsMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1199 static void SelectDealersOfferSlotsRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1200 static void SelectPlayersOfferSlotsMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1201 static void SelectPlayersOfferSlotsRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
1202 
1203 
CreateSkiInventorySlotMouseRegions(void)1204 static void CreateSkiInventorySlotMouseRegions(void)
1205 {
1206 	MSYS_DefineRegion(&g_dealer_inventory_scroll_region, 161, 27, 532, 137, MSYS_PRIORITY_HIGH, CURSOR_NORMAL, NULL, DealerInventoryScrollRegionCallback);
1207 
1208 	bool const does_repairs = DoesDealerDoRepairs(gbSelectedArmsDealerID);
1209 
1210 	// Create the mouse regions for the shopkeeper's inventory
1211 	for (UINT32 i = 0; i != SKI_NUM_ARMS_DEALERS_INV_SLOTS; ++i)
1212 	{
1213 		UINT16 const x = SKI_ARMS_DEALERS_INV_START_X + i % SKI_NUM_ARMS_DEALERS_INV_COLS * SKI_INV_OFFSET_X;
1214 		UINT16 const y = SKI_ARMS_DEALERS_INV_START_Y + i / SKI_NUM_ARMS_DEALERS_INV_COLS * SKI_INV_OFFSET_Y;
1215 		{
1216 			MOUSE_REGION* const r = &gDealersInventoryMouseRegions[i];
1217 			MSYS_DefineRegion(r, x, y, x + SKI_INV_SLOT_WIDTH, y + SKI_INV_SLOT_HEIGHT, MSYS_PRIORITY_HIGH, CURSOR_NORMAL, SelectDealersInventoryMovementRegionCallBack, SelectDealersInventoryRegionCallBack);
1218 			MSYS_SetRegionUserData(r, 0, i);
1219 		}
1220 		if (does_repairs)
1221 		{
1222 			// Small Faces
1223 			MOUSE_REGION* const r = &gRepairmanInventorySmallFaceMouseRegions[i];
1224 			MSYS_DefineRegion(r, x + SKI_SMALL_FACE_OFFSET_X, y, x + SKI_SMALL_FACE_OFFSET_X + SKI_SMALL_FACE_WIDTH, y + SKI_SMALL_FACE_HEIGHT, MSYS_PRIORITY_HIGH + 1, CURSOR_NORMAL, NULL, NULL);
1225 			MSYS_SetRegionUserData(r, 0, i);
1226 		}
1227 	}
1228 
1229 	// Create the mouse regions for the shopkeeper's trading slots
1230 	for (UINT32 i = 0; i != SKI_NUM_TRADING_INV_SLOTS; ++i)
1231 	{
1232 		UINT16 const x = SKI_ARMS_DEALERS_TRADING_INV_X + i % SKI_NUM_TRADING_INV_COLS * SKI_INV_OFFSET_X;
1233 		UINT16 const y = SKI_ARMS_DEALERS_TRADING_INV_Y + i / SKI_NUM_TRADING_INV_COLS * SKI_INV_OFFSET_Y;
1234 		{
1235 			MOUSE_REGION* const r = &gDealersOfferSlotsMouseRegions[i];
1236 			MSYS_DefineRegion(r, x, y, x + SKI_INV_SLOT_WIDTH, y + SKI_INV_SLOT_HEIGHT, MSYS_PRIORITY_HIGH, CURSOR_NORMAL, SelectDealersOfferSlotsMovementRegionCallBack, SelectDealersOfferSlotsRegionCallBack);
1237 			MSYS_SetRegionUserData(r, 0, i);
1238 		}
1239 		if (does_repairs)
1240 		{
1241 			// Small Faces
1242 			MOUSE_REGION* const r = &gDealersOfferSlotsSmallFaceMouseRegions[i];
1243 			MSYS_DefineRegion(r, x + SKI_SMALL_FACE_OFFSET_X, y, x + SKI_SMALL_FACE_OFFSET_X + SKI_SMALL_FACE_WIDTH, y + SKI_SMALL_FACE_HEIGHT, MSYS_PRIORITY_HIGH + 1, CURSOR_NORMAL, SelectDealersOfferSlotsMovementRegionCallBack, SelectDealersOfferSlotsRegionCallBack);
1244 			MSYS_SetRegionUserData(r, 0, i);
1245 		}
1246 	}
1247 
1248 	// Create the mouse regions for the Players trading slots
1249 	for (UINT32 i = 0; i != SKI_NUM_TRADING_INV_SLOTS; ++i)
1250 	{
1251 		UINT16 const x = SKI_PLAYERS_TRADING_INV_X + i % SKI_NUM_TRADING_INV_COLS * SKI_INV_OFFSET_X;
1252 		UINT16 const y = SKI_PLAYERS_TRADING_INV_Y + i / SKI_NUM_TRADING_INV_COLS * SKI_INV_OFFSET_Y;
1253 		{
1254 			// Trading Slots
1255 			MOUSE_REGION* const r = &gPlayersOfferSlotsMouseRegions[i];
1256 			MSYS_DefineRegion(r, x, y, x + SKI_INV_SLOT_WIDTH, y + SKI_INV_SLOT_HEIGHT, MSYS_PRIORITY_HIGH, CURSOR_NORMAL, SelectPlayersOfferSlotsMovementRegionCallBack, SelectPlayersOfferSlotsRegionCallBack);
1257 			MSYS_SetRegionUserData(r, 0, i);
1258 		}
1259 		{
1260 			// Small Faces
1261 			MOUSE_REGION* const r = &gPlayersOfferSlotsSmallFaceMouseRegions[i];
1262 			MSYS_DefineRegion(r, x + SKI_SMALL_FACE_OFFSET_X, y, x + SKI_SMALL_FACE_OFFSET_X + SKI_SMALL_FACE_WIDTH, y + SKI_SMALL_FACE_HEIGHT, MSYS_PRIORITY_HIGH + 1, CURSOR_NORMAL, SelectPlayersOfferSlotsMovementRegionCallBack, SelectPlayersOfferSlotsRegionCallBack);
1263 			MSYS_SetRegionUserData(r, 0, i);
1264 		}
1265 	}
1266 }
1267 
1268 
DestroySkiInventorySlotMouseRegions(void)1269 static void DestroySkiInventorySlotMouseRegions(void)
1270 {
1271 	MSYS_RemoveRegion(&g_dealer_inventory_scroll_region);
1272 
1273 	bool const does_repairs = DoesDealerDoRepairs(gbSelectedArmsDealerID);
1274 
1275 	for (UINT32 i = 0; i != SKI_NUM_ARMS_DEALERS_INV_SLOTS; ++i)
1276 	{
1277 		MSYS_RemoveRegion(&gDealersInventoryMouseRegions[i]);
1278 		if (does_repairs) MSYS_RemoveRegion(&gRepairmanInventorySmallFaceMouseRegions[i]);
1279 	}
1280 
1281 	for (UINT32 i = 0; i != SKI_NUM_TRADING_INV_SLOTS; ++i)
1282 	{
1283 		MSYS_RemoveRegion(&gDealersOfferSlotsMouseRegions[i]);
1284 		if (does_repairs) MSYS_RemoveRegion(&gDealersOfferSlotsSmallFaceMouseRegions[i]);
1285 
1286 		MSYS_RemoveRegion(&gPlayersOfferSlotsMouseRegions[i]);
1287 		MSYS_RemoveRegion(&gPlayersOfferSlotsSmallFaceMouseRegions[i]);
1288 	}
1289 }
1290 
1291 
1292 static INT8 AddItemToArmsDealerOfferArea(const INVENTORY_IN_SLOT* pInvSlot, INT8 bSlotIdInOtherLocation);
1293 static void InitShopKeeperItemDescBox(OBJECTTYPE* pObject, UINT8 ubPocket, UINT8 ubFromLocation);
1294 
1295 
1296 //Mouse Call back for the Arms traders inventory slot
SelectDealersInventoryRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1297 static void SelectDealersInventoryRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1298 {
1299 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1300 	{
1301 		UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1302 		INT8  ubLocation;
1303 
1304 		if( gpTempDealersInventory == NULL )
1305 			return;
1306 
1307 		ubSelectedInvSlot += gSelectArmsDealerInfo.ubFirstItemIndexOnPage;
1308 
1309 		//if the selected slot is above any inventory we have, return
1310 		if( ubSelectedInvSlot >= gSelectArmsDealerInfo.uiNumDistinctInventoryItems )
1311 			return;
1312 
1313 		//if there are any items still there
1314 		if( gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects > 0 )
1315 		{
1316 			//If the item type has not already been placed
1317 			if( !( gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags & ARMS_INV_ITEM_SELECTED ) )
1318 			{
1319 				//if the dealer repairs
1320 				if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1321 				{
1322 					// ignore left clicks on items under repair.  Fully repaired items are moved out to player's slots automatically
1323 				}
1324 				else // not a repairman
1325 				{
1326 					ubLocation = AddItemToArmsDealerOfferArea( &gpTempDealersInventory[ ubSelectedInvSlot ], ubSelectedInvSlot );
1327 
1328 					//if the item was added to the Dealer Offer Area correctly
1329 					if( ubLocation != -1 )
1330 					{
1331 						//Set the flag indicating the item has been selected
1332 						gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags |= ARMS_INV_ITEM_SELECTED;
1333 
1334 						//Specify the location the items went to
1335 						gpTempDealersInventory[ ubSelectedInvSlot ].ubLocationOfObject = ARMS_DEALER_OFFER_AREA;
1336 						gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation = ubLocation;
1337 
1338 						//if the shift key is being pressed, remove them all
1339 						if (_KeyDown(SHIFT))
1340 						{
1341 							gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects = 0;
1342 						}
1343 						else
1344 						{
1345 							gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects --;
1346 						}
1347 
1348 						gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1349 					}
1350 				}
1351 			}
1352 			else // some of this item are already in dealer's offer area
1353 			{
1354 				UINT8 ubNumToMove;
1355 
1356 				//if the shift key is being pressed, remove them all
1357 				if (_KeyDown(SHIFT))
1358 				{
1359 					ubNumToMove = gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects;
1360 				}
1361 				else
1362 				{
1363 					ubNumToMove = 1;
1364 				}
1365 
1366 				//Reduce the number in dealer's inventory
1367 				gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects -= ubNumToMove;
1368 				//Increase the number in dealer's offer area
1369 				ArmsDealerOfferArea[ gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.ubNumberOfObjects += ubNumToMove;
1370 
1371 				gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1372 			}
1373 		}
1374 	}
1375 
1376 	else if (iReason & MSYS_CALLBACK_REASON_RBUTTON_UP)
1377 	{
1378 		UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1379 
1380 		if( gpTempDealersInventory == NULL )
1381 			return;
1382 
1383 		ubSelectedInvSlot += gSelectArmsDealerInfo.ubFirstItemIndexOnPage;
1384 
1385 		//if the selected slot is above any inventory we have, return
1386 		if( ubSelectedInvSlot >= gSelectArmsDealerInfo.uiNumDistinctInventoryItems )
1387 			return;
1388 
1389 		//DEF:
1390 		//bring up the item description box
1391 		if ( !InItemDescriptionBox( ) )
1392 		{
1393 			InitShopKeeperItemDescBox( &gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject, ubSelectedInvSlot, ARMS_DEALER_INVENTORY );
1394 		}
1395 		else
1396 		{
1397 			DeleteItemDescriptionBox( );
1398 		}
1399 
1400 
1401 
1402 		/*
1403 		//if the item has been seleceted
1404 		if( gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags & ARMS_INV_ITEM_SELECTED )
1405 		{
1406 			//Check to see it there is more then 1 item in the location
1407 			INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[gpTempDealersInventory[ubSelectedInvSlot].bSlotIdInOtherLocation];
1408 			if (a->ItemObject.ubNumberOfObjects > 0)
1409 			{
1410 				//Increase the number in the dealer inventory
1411 				gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects ++;
1412 
1413 				//Decrease the number in the dealer offer area
1414 				a->ItemObject.ubNumberOfObjects--;
1415 
1416 				//if there is nothing left in the arms dealer offer area
1417 				if (a->ItemObject.ubNumberOfObjects == 0)
1418 				{
1419 					RemoveItemFromArmsDealerOfferArea( gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation, FALSE );
1420 				}
1421 
1422 				//redraw the screen
1423 				gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1424 			}
1425 		}*/
1426 	}
1427 	else if (iReason & MSYS_CALLBACK_REASON_WHEEL_UP)
1428 	{
1429 		ShopInventoryPageUp();
1430 	}
1431 	else if (iReason & MSYS_CALLBACK_REASON_WHEEL_DOWN)
1432 	{
1433 		ShopInventoryPageDown();
1434 	}
1435 }
1436 
1437 
SelectDealersInventoryMovementRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1438 static void SelectDealersInventoryMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1439 {
1440 	UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1441 	ubSelectedInvSlot += gSelectArmsDealerInfo.ubFirstItemIndexOnPage;
1442 
1443 	if( gpTempDealersInventory == NULL )
1444 		return;
1445 
1446 	if( ubSelectedInvSlot >= gSelectArmsDealerInfo.uiNumDistinctInventoryItems )
1447 		return;
1448 
1449 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE)
1450 	{
1451 		//if there is nothing in the slot, exit
1452 		if (!gpTempDealersInventory[ubSelectedInvSlot].fActive)
1453 			return;
1454 
1455 		gpHighLightedItemObject = &gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject;
1456 
1457 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE );
1458 	}
1459 	else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
1460 	{
1461 		//if there is nothing in the slot, exit
1462 		if (!gpTempDealersInventory[ubSelectedInvSlot].fActive)
1463 			return;
1464 
1465 		gpHighLightedItemObject = NULL;
1466 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
1467 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE );
1468 	}
1469 }
1470 
1471 
SelectDealersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1472 static void SelectDealersOfferSlotsMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1473 {
1474 	UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1475 	const INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[ubSelectedInvSlot];
1476 
1477 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE)
1478 	{
1479 		//if there is nothing in the slot, exit
1480 		if (!a->fActive) return;
1481 
1482 		gpHighLightedItemObject = &a->ItemObject;
1483 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE );
1484 	}
1485 	else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
1486 	{
1487 		//if there is nothing in the slot, exit
1488 		if (!a->fActive) return;
1489 
1490 		gpHighLightedItemObject = NULL;
1491 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
1492 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE );
1493 	}
1494 }
1495 
1496 
SelectPlayersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1497 static void SelectPlayersOfferSlotsMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1498 {
1499 	UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1500 	const INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubSelectedInvSlot];
1501 
1502 	if (iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE)
1503 	{
1504 		//if there is nothing in the slot, exit
1505 		if (!o->fActive) return;
1506 
1507 		gpHighLightedItemObject = &o->ItemObject;
1508 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE );
1509 	}
1510 	else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
1511 	{
1512 		//if there is nothing in the slot, exit
1513 		if (!o->fActive) return;
1514 
1515 		gpHighLightedItemObject = NULL;
1516 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
1517 		HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE );
1518 	}
1519 }
1520 
1521 
1522 static INT8 AddInventoryToSkiLocation(const INVENTORY_IN_SLOT* pInv, UINT8 ubSpotLocation, UINT8 ubWhere);
1523 static void IfMercOwnedCopyItemToMercInv(const INVENTORY_IN_SLOT* pInv);
1524 static void IfMercOwnedRemoveItemFromMercInv(const INVENTORY_IN_SLOT* pInv);
1525 static BOOLEAN RemoveItemFromArmsDealerOfferArea(INT8 bSlotId, BOOLEAN fKeepItem);
1526 static BOOLEAN RemoveRepairItemFromDealersOfferArea(INT8 bSlot);
1527 
1528 
1529 //Mouse Call back for the dealer's OFFER slot
SelectDealersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1530 static void SelectDealersOfferSlotsRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1531 {
1532 	UINT8	ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1533 	INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[ubSelectedInvSlot];
1534 
1535 	if (iReason & MSYS_CALLBACK_REASON_RBUTTON_UP)
1536 	{
1537 		//if there is something here
1538 		if (a->fActive)
1539 		{
1540 			//if the dealer repairs
1541 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1542 			{
1543 				// return item to player
1544 				RemoveRepairItemFromDealersOfferArea( ubSelectedInvSlot );
1545 			}
1546 			else
1547 			{
1548 				//bring up the item description box
1549 				if ( !InItemDescriptionBox( ) )
1550 				{
1551 					InitShopKeeperItemDescBox(&a->ItemObject, ubSelectedInvSlot, ARMS_DEALER_OFFER_AREA);
1552 				}
1553 				else
1554 				{
1555 					DeleteItemDescriptionBox( );
1556 				}
1557 			}
1558 		}
1559 	}
1560 	else if( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) //MSYS_CALLBACK_REASON_LBUTTON_UP)
1561 	{
1562 		/*
1563 		//if the current merc is disabled for whatever reason
1564 		if( gfSMDisableForItems )
1565 			// the
1566 			return;*/
1567 		//if there is something here
1568 		if (a->fActive)
1569 		{
1570 			//if this is a repair dealer
1571 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1572 			{
1573 				//if we don't have an item, pick one up
1574 				if( gMoveingItem.sItemIndex == 0 )
1575 				{
1576 					//if the dealer is a repair dealer, allow the player to pick up the item
1577 					if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1578 					{
1579 						BeginSkiItemPointer( ARMS_DEALER_OFFER_AREA, ubSelectedInvSlot, FALSE );
1580 					}
1581 				}
1582 				else
1583 				{
1584 					//swap what is in the cursor with what is in the player offer slot
1585 
1586 					// if the slot is overloaded (holds more objects than we have room for valid statuses of)
1587 					if (a->ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT)
1588 					{
1589 						// then ignore the click - we can't do the swap, or anything very useful, because we can't allow overloaded
1590 						// items into the player's cursor - if he put them into a merc's inventory, the extra statuses are missing!
1591 						// At best, we could do some sort of message here.
1592 						return;
1593 					}
1594 
1595 					IfMercOwnedCopyItemToMercInv( &gMoveingItem );
1596 
1597 					const INVENTORY_IN_SLOT TempSlot = *a;
1598 					*a = gMoveingItem;
1599 					gMoveingItem = TempSlot;
1600 
1601 					IfMercOwnedRemoveItemFromMercInv( &gMoveingItem );
1602 
1603 					// Change mouse cursor
1604 					gpItemPointer = &gMoveingItem.ItemObject;
1605 					SetSkiCursor( EXTERN_CURSOR );
1606 
1607 					gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1608 				}
1609 			}
1610 			else //non-repairman
1611 			{
1612 				//if the shift key is being pressed, remove them all
1613 				if (_KeyDown(SHIFT) || a->ItemObject.ubNumberOfObjects == 1)
1614 				{
1615 					RemoveItemFromArmsDealerOfferArea(ubSelectedInvSlot, TRUE);//a->bSlotIdInOtherLocation
1616 				}
1617 				else	// multiple items there, SHIFT isn't being pressed
1618 				{
1619 					// remove only one at a time
1620 					a->ItemObject.ubNumberOfObjects--;
1621 					gpTempDealersInventory[a->bSlotIdInOtherLocation].ItemObject.ubNumberOfObjects++;
1622 				}
1623 
1624 				gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1625 			}
1626 		}
1627 		else // empty slot
1628 		{
1629 			//if the cursor has something in it
1630 			if( gMoveingItem.sItemIndex > 0 )
1631 			{
1632 				// we'd better talking to a repairman, cursor is locked out of this area while full for non-repairmen!
1633 				Assert(DoesDealerDoRepairs(gbSelectedArmsDealerID));
1634 
1635 				//Drop the item into the current slot
1636 				AddInventoryToSkiLocation( &gMoveingItem, ubSelectedInvSlot, ARMS_DEALER_OFFER_AREA );
1637 
1638 				//Reset the cursor
1639 				SetSkiCursor( CURSOR_NORMAL );
1640 
1641 				//refresh the screen
1642 				gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1643 			}
1644 		}
1645 	}
1646 }
1647 
1648 
1649 static BOOLEAN RemoveItemFromPlayersOfferArea(INT8 bSlot);
1650 
1651 
1652 //Mouse Call back for the Players OFFER slot
SelectPlayersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)1653 static void SelectPlayersOfferSlotsRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
1654 {
1655 	UINT8 ubSelectedInvSlot = (UINT8)MSYS_GetRegionUserData( pRegion, 0 );
1656 	INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubSelectedInvSlot];
1657 	INT8 bAddedToSlotID = -1;
1658 
1659 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
1660 	{
1661 		//if the cursor has no item in it
1662 		if( gMoveingItem.sItemIndex == 0 )
1663 		{
1664 			//if there is nothing here, return
1665 			if (!o->fActive)
1666 				return;
1667 
1668 			// pick it up into the cursor
1669 			BeginSkiItemPointer( PLAYERS_OFFER_AREA, ubSelectedInvSlot, FALSE );
1670 		}
1671 		else	// we have something in the cursor
1672 		{
1673 			//Drop the item into the current slot
1674 			//if there is something already there
1675 			if (o->fActive)
1676 			{
1677 				//swap what is in the cursor with what is in the player offer slot
1678 
1679 				// if the slot is overloaded (holds more objects than we have room for valid statuses of)
1680 				if (o->ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT)
1681 				{
1682 					// then ignore the click - we can't do the swap, or anything very useful, because we can't allow overloaded
1683 					// items into the player's cursor - if he put them into a merc's inventory, the extra statuses are missing!
1684 					// At best, we could do some sort of message here.
1685 					return;
1686 				}
1687 
1688 				IfMercOwnedCopyItemToMercInv( &gMoveingItem );
1689 
1690 				const INVENTORY_IN_SLOT TempSlot = *o;
1691 				*o = gMoveingItem;
1692 				gMoveingItem = TempSlot;
1693 
1694 				IfMercOwnedRemoveItemFromMercInv( &gMoveingItem );
1695 
1696 				// Change mouse cursor
1697 				gpItemPointer = &gMoveingItem.ItemObject;
1698 				SetSkiCursor( EXTERN_CURSOR );
1699 
1700 				//if the item we are adding is money
1701 				if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
1702 				{
1703 					//Since money is always evaluated
1704 					o->uiFlags     |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE;
1705 					o->uiItemPrice  = o->ItemObject.bMoneyStatus;
1706 				}
1707 			}
1708 			else	// slot is empty
1709 			{
1710 				// if the item has already been evaluated, or has just been purchased
1711 				if ( ( gMoveingItem.uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED ) ||
1712 					( gMoveingItem.uiFlags & ARMS_INV_JUST_PURCHASED ) )
1713 				{
1714 					//place the item that is in the player's hand into this player offer area slot
1715 					bAddedToSlotID = AddInventoryToSkiLocation( &gMoveingItem, ubSelectedInvSlot, PLAYERS_OFFER_AREA );
1716 					Assert ( bAddedToSlotID != -1 );
1717 				}
1718 				else
1719 				{
1720 					// this splits complex items for repairs.  Also puts things into the first free POA slot
1721 					OfferObjectToDealer( &gMoveingItem.ItemObject, gMoveingItem.ubIdOfMercWhoOwnsTheItem, gMoveingItem.bSlotIdInOtherLocation );
1722 				}
1723 
1724 				//Reset the cursor
1725 				SetSkiCursor( CURSOR_NORMAL );
1726 			}
1727 
1728 			//Dirty the interface
1729 			gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1730 		}
1731 	}
1732 	else if(iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) //MSYS_CALLBACK_REASON_LBUTTON_UP)
1733 	{
1734 		//if the box is active
1735 		if (o->fActive)
1736 		{
1737 			RemoveItemFromPlayersOfferArea( ubSelectedInvSlot );
1738 			/*
1739 			item description
1740 			else
1741 			{
1742 				if ( !InItemDescriptionBox( ) )
1743 				{
1744 					InitItemDescriptionBox(gpSMCurrentMerc, o->bSlotIdInOtherLocation, 214, 1 + INV_INTERFACE_START_Y, 0);
1745 				}
1746 				else
1747 				{
1748 					DeleteItemDescriptionBox( );
1749 				}
1750 			}*/
1751 		}
1752 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
1753 	}
1754 }
1755 
1756 
EnterShopKeeperInterfaceScreen(UINT8 ubArmsDealer)1757 void EnterShopKeeperInterfaceScreen( UINT8 ubArmsDealer )
1758 {
1759 	//Get Dealer ID from from merc Id
1760 	gbSelectedArmsDealerID = GetArmsDealerIDFromMercID( ubArmsDealer );
1761 
1762 	if( gbSelectedArmsDealerID == -1 )
1763 	{
1764 		SLOGW("Failed to find Arms Dealer ID From Merc ID #%d", ubArmsDealer );
1765 		gfSKIScreenExit = TRUE;
1766 	}
1767 
1768 	LeaveTacticalScreen( SHOPKEEPER_SCREEN );
1769 }
1770 
1771 
1772 static void DetermineArmsDealersSellingInventory(void);
1773 
1774 
InitializeShopKeeper(BOOLEAN fResetPage)1775 static void InitializeShopKeeper(BOOLEAN fResetPage)
1776 {
1777 	// update time player last dealt with him
1778 	gArmsDealerStatus[ gbSelectedArmsDealerID ].uiTimePlayerLastInSKI = GetWorldTotalMin();
1779 
1780 
1781 	//Get the number of distinct items in the inventory
1782 	gSelectArmsDealerInfo.uiNumDistinctInventoryItems = CountDistinctItemsInArmsDealersInventory( gbSelectedArmsDealerID );
1783 
1784 	// If there are any items in the dealer's inventory
1785 	if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems > 0 )
1786 	{
1787 		//Create the shopkeeper's temp inventory
1788 		DetermineArmsDealersSellingInventory( );
1789 
1790 		/* No longer necessary. ARM
1791 		//Some of the number might have changed in DetermineArmsDealersSellingInventory(), recalc the values
1792 		gSelectArmsDealerInfo.uiNumDistinctInventoryItems = CountDistinctItemsInArmsDealersInventory( gbSelectedArmsDealerID );*/
1793 	}
1794 
1795 	gSelectArmsDealerInfo.ubNumberOfPages = (UINT8)( gSelectArmsDealerInfo.uiNumDistinctInventoryItems / SKI_NUM_ARMS_DEALERS_INV_SLOTS );
1796 	if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems % 15 != 0 )
1797 		gSelectArmsDealerInfo.ubNumberOfPages += 1;
1798 
1799 
1800 	//Should we reset the current inventory page being displayed
1801 	if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems == 0 )
1802 		gSelectArmsDealerInfo.ubCurrentPage = 0;
1803 	else
1804 	{
1805 		if( fResetPage )
1806 		{
1807 			if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems == 0 )
1808 				gSelectArmsDealerInfo.ubCurrentPage = 0;
1809 			else
1810 				gSelectArmsDealerInfo.ubCurrentPage = 1;
1811 		}
1812 
1813 		//or if the current page will be an invalid page ( before the first, and after the last )
1814 		#if 0 /* XXX unsigned */
1815 		else if( gSelectArmsDealerInfo.ubCurrentPage < 0 || gSelectArmsDealerInfo.ubCurrentPage > gSelectArmsDealerInfo.ubNumberOfPages )
1816 		#else
1817 		else if (gSelectArmsDealerInfo.ubCurrentPage > gSelectArmsDealerInfo.ubNumberOfPages)
1818 		#endif
1819 		{
1820 			gSelectArmsDealerInfo.ubCurrentPage = 1;
1821 		}
1822 
1823 		else if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems != 0 )
1824 			gSelectArmsDealerInfo.ubCurrentPage = 1;
1825 	}
1826 
1827 
1828 	//if there is no inventory
1829 	if( gSelectArmsDealerInfo.uiNumDistinctInventoryItems == 0 )
1830 	{
1831 		gSelectArmsDealerInfo.ubCurrentPage = 0;
1832 		gSelectArmsDealerInfo.ubNumberOfPages = 0;
1833 
1834 		//disable the page up/down buttons
1835 		DisableButton( guiSKI_InvPageUpButton );
1836 		DisableButton( guiSKI_InvPageDownButton );
1837 
1838 		return;
1839 	}
1840 	else
1841 	{
1842 		//enable the page up/down buttons
1843 		EnableButton( guiSKI_InvPageUpButton );
1844 		EnableButton( guiSKI_InvPageDownButton );
1845 	}
1846 
1847 
1848 	EnableDisableDealersInventoryPageButtons();
1849 }
1850 
1851 
CalculateFirstItemIndexOnPage(void)1852 static void CalculateFirstItemIndexOnPage(void)
1853 {
1854 	gSelectArmsDealerInfo.ubFirstItemIndexOnPage = ( gSelectArmsDealerInfo.ubCurrentPage -1 ) * SKI_NUM_ARMS_DEALERS_INV_SLOTS;
1855 }
1856 
1857 
1858 static UINT32 DisplayInvSlot(UINT8 slot_num, UINT16 usItemIndex, UINT16 x, UINT16 y, OBJECTTYPE const& pItemObject, bool hatched_out, UINT8 ubItemArea);
1859 static void HatchOutInvSlot(UINT16 usPosX, UINT16 usPosY);
1860 static void SetSkiFaceRegionHelpText(const INVENTORY_IN_SLOT* pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea);
1861 static void SetSkiRegionHelpText(const INVENTORY_IN_SLOT* pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea);
1862 
1863 
DisplayArmsDealerCurrentInventoryPage(void)1864 static void DisplayArmsDealerCurrentInventoryPage(void)
1865 {
1866 	ST::string zTemp;
1867 	UINT16  uiFontHeight;
1868 	UINT16  usCnt=0;
1869 	UINT16  usPosX, usPosY;
1870 	UINT8   sItemCount=0;
1871 	BOOLEAN fDisplayHatchOnItem=FALSE;
1872 
1873 
1874 	usPosX = SKI_ARMS_DEALERS_INV_START_X;
1875 	usPosY = SKI_ARMS_DEALERS_INV_START_Y;
1876 
1877 	//if there is any inventory
1878 	if( gpTempDealersInventory != NULL )
1879 	{
1880 		if( gubSkiDirtyLevel != SKI_DIRTY_LEVEL0 )
1881 		{
1882 			//Calculate the item that is at the top of the list
1883 			CalculateFirstItemIndexOnPage( );
1884 
1885 			//Restore the background before blitting the text back on
1886 			RestoreExternBackgroundRect( SKI_PAGE_X, SKI_PAGE_Y, SKI_PAGE_WIDTH, SKI_PAGE_HEIGHT );
1887 
1888 			//Restore the pristine region
1889 			RestoreExternBackgroundRect( SKI_ARMS_DEALERS_INV_START_X, SKI_ARMS_DEALERS_INV_START_Y, 370, 107 );
1890 
1891 			//Display the current inventory page
1892 			DrawTextToScreen(SKI_Text[SKI_TEXT_PAGE], SKI_PAGE_X, SKI_PAGE_Y + 3, SKI_PAGE_WIDTH, SKI_LABEL_FONT, SKI_TITLE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
1893 
1894 			//Display the Current Page number
1895 			uiFontHeight = GetFontHeight( SKI_LABEL_FONT );
1896 			zTemp = ST::format("{}/{}", gSelectArmsDealerInfo.ubCurrentPage, gSelectArmsDealerInfo.ubNumberOfPages);
1897 			DrawTextToScreen(zTemp, SKI_PAGE_X, SKI_PAGE_Y + uiFontHeight + 6, SKI_PAGE_WIDTH, SKI_LABEL_FONT, SKI_TITLE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
1898 		}
1899 
1900 		//Display all the items for the current page
1901 		for( usCnt=gSelectArmsDealerInfo.ubFirstItemIndexOnPage; ( ( usCnt<gSelectArmsDealerInfo.uiNumDistinctInventoryItems ) && ( sItemCount < SKI_NUM_ARMS_DEALERS_INV_SLOTS ) ); usCnt++ )
1902 		{
1903 			//if the item is still in the inventory
1904 			if( gpTempDealersInventory[ usCnt ].sItemIndex != 0 )
1905 			{
1906 				// hatch it out if region is disabled
1907 				if ( !( gDealersInventoryMouseRegions[ sItemCount ].uiFlags & MSYS_REGION_ENABLED ) )
1908 				{
1909 					fDisplayHatchOnItem = TRUE;
1910 				}
1911 				//if the dealer repairs
1912 				else if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1913 				{
1914 					// not displaying the hatch, because it would obscure the repair ETA
1915 					fDisplayHatchOnItem = FALSE;
1916 				}
1917 				else // non-repairman
1918 				{
1919 					// check if none left
1920 					if ( gpTempDealersInventory[ usCnt ].ItemObject.ubNumberOfObjects == 0 )
1921 					{
1922 						fDisplayHatchOnItem = TRUE;
1923 					}
1924 					else
1925 					{
1926 						fDisplayHatchOnItem = FALSE;
1927 					}
1928 				}
1929 
1930 				// Display the inventory slot
1931 				INVENTORY_IN_SLOT const& inv = gpTempDealersInventory[usCnt];
1932 				DisplayInvSlot(usCnt, inv.sItemIndex, usPosX, usPosY, inv.ItemObject, fDisplayHatchOnItem, ARMS_DEALER_INVENTORY);
1933 
1934 				if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
1935 				{
1936 					SetSkiRegionHelpText( &gpTempDealersInventory[ usCnt ], &gDealersInventoryMouseRegions[sItemCount], ARMS_DEALER_INVENTORY );
1937 
1938 					//if the dealer repairs
1939 					if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1940 					{
1941 						SetSkiFaceRegionHelpText( &gpTempDealersInventory[ usCnt ], &gRepairmanInventorySmallFaceMouseRegions[sItemCount], ARMS_DEALER_INVENTORY );
1942 					}
1943 				}
1944 
1945 				sItemCount++;
1946 
1947 				usPosX += SKI_INV_OFFSET_X;
1948 
1949 				//if we are on to the next row
1950 				if( !( sItemCount % SKI_NUM_ARMS_DEALERS_INV_COLS ) )
1951 				{
1952 					usPosX = SKI_ARMS_DEALERS_INV_START_X;
1953 					usPosY += SKI_INV_OFFSET_Y;
1954 				}
1955 			}
1956 		}
1957 	}
1958 
1959 
1960 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
1961 	{
1962 		//This handles the remaining (empty) slots, resetting Fast Help text, and hatching out disabled ones
1963 		while ( sItemCount < SKI_NUM_ARMS_DEALERS_INV_SLOTS )
1964 		{
1965 			SetSkiRegionHelpText( NULL, &gDealersInventoryMouseRegions[ sItemCount ], ARMS_DEALER_INVENTORY );
1966 
1967 			//if the dealer repairs
1968 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
1969 			{
1970 				SetSkiFaceRegionHelpText( NULL, &gRepairmanInventorySmallFaceMouseRegions[ sItemCount ], ARMS_DEALER_INVENTORY );
1971 			}
1972 
1973 			// hatch it out if region is disabled
1974 			if ( !( gDealersInventoryMouseRegions[ sItemCount ].uiFlags & MSYS_REGION_ENABLED ) )
1975 			{
1976 				HatchOutInvSlot( usPosX, usPosY );
1977 			}
1978 
1979 			sItemCount++;
1980 
1981 			usPosX += SKI_INV_OFFSET_X;
1982 
1983 			//if we are on to the next row
1984 			if( !( sItemCount % SKI_NUM_ARMS_DEALERS_INV_COLS ) )
1985 			{
1986 				usPosX = SKI_ARMS_DEALERS_INV_START_X;
1987 				usPosY += SKI_INV_OFFSET_Y;
1988 			}
1989 		}
1990 	}
1991 }
1992 
1993 
1994 static ST::string BuildDoneWhenTimeString(ArmsDealerID, UINT16 usItemIndex, UINT8 ubElement);
1995 static UINT32 CalcShopKeeperItemPrice(BOOLEAN fDealerSelling, BOOLEAN fUnitPriceOnly, UINT16 usItemID, FLOAT dModifier, const OBJECTTYPE* pItemObject);
1996 static INT8 GetSlotNumberForMerc(UINT8 ubProfile);
1997 static bool IsGunOrAmmoOfSameTypeSelected(OBJECTTYPE const&);
1998 
1999 
DisplayInvSlot(UINT8 const slot_num,UINT16 const item_idx,UINT16 const x,UINT16 const y,OBJECTTYPE const & item_o,bool const hatched_out,UINT8 const item_area)2000 static UINT32 DisplayInvSlot(UINT8 const slot_num, UINT16 const item_idx, UINT16 const x, UINT16 const y, OBJECTTYPE const& item_o, bool const hatched_out, UINT8 const item_area)
2001 {
2002 	ST::string buf;
2003 
2004 	UINT16 outline;
2005 	if (IsGunOrAmmoOfSameTypeSelected(item_o))
2006 	{
2007 		outline = Get16BPPColor(FROMRGB(255, 255, 255));
2008 	}
2009 	else if (gubSkiDirtyLevel != SKI_DIRTY_LEVEL0)
2010 	{
2011 		outline = SGP_TRANSPARENT;
2012 	}
2013 	else
2014 	{
2015 		// The item is not highlighted and we are not rerendering the screen
2016 		return 0;
2017 	}
2018 
2019 	// Restore the background region
2020 	RestoreExternBackgroundRect(x, y, SKI_INV_SLOT_WIDTH, SKI_INV_HEIGHT);
2021 
2022 	{
2023 		// Display the item graphic
2024 		const ItemModel * item = GCM->getItem(item_idx);
2025 		SGPVObject  const& item_vo = GetInterfaceGraphicForItem(item);
2026 		ETRLEObject const& e       = item_vo.SubregionProperties(item->getGraphicNum());
2027 		INT16              cen_x   = x + 7 + ABS(SKI_INV_WIDTH - 3 - e.usWidth)  / 2 - e.sOffsetX;
2028 		INT16              cen_y   = y +     ABS(SKI_INV_HEIGHT    - e.usHeight) / 2 - e.sOffsetY;
2029 		if (gamepolicy(f_draw_item_shadow))
2030 		{
2031 			BltVideoObjectOutlineShadow(FRAME_BUFFER, &item_vo, item->getGraphicNum(), cen_x - 2, cen_y + 2);
2032 		}
2033 		BltVideoObjectOutline(      FRAME_BUFFER, &item_vo, item->getGraphicNum(), cen_x,     cen_y, outline);	}
2034 
2035 	{
2036 		// Display the status of the item
2037 		UINT16 const colour = Get16BPPColor(FROMRGB(140, 136, 119));
2038 		DrawItemUIBarEx(item_o, 0, x + 2, y + 21, 20, colour, colour, FRAME_BUFFER);
2039 	}
2040 
2041 	// Display the item's cost
2042 	bool      print_repaired = FALSE;
2043 	UINT32    item_cost      = 0;
2044 	ProfileID owner          = NO_PROFILE;
2045 	if (item_area == PLAYERS_OFFER_AREA)
2046 	{
2047 		INVENTORY_IN_SLOT const& o = PlayersOfferArea[slot_num];
2048 		if (o.uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE) item_cost = o.uiItemPrice;
2049 		owner          = o.ubIdOfMercWhoOwnsTheItem;
2050 		print_repaired = o.uiFlags & ARMS_INV_ITEM_REPAIRED;
2051 	}
2052 	else if (item_area == ARMS_DEALER_INVENTORY)
2053 	{
2054 		const DealerModel* dealer_info = SelectedArmsDealer();
2055 		if (!DoesDealerDoRepairs(gbSelectedArmsDealerID))
2056 		{
2057 			if (!hatched_out || item_o.ubNumberOfObjects != 0)
2058 			{
2059 				// Show the unit price, not the total value of all if stacked
2060 				item_cost = CalcShopKeeperItemPrice(DEALER_SELLING, TRUE, item_idx, dealer_info->sellingPrice, &item_o);
2061 			}
2062 		}
2063 		else
2064 		{
2065 			// Display the length of time needed to repair the item
2066 			INVENTORY_IN_SLOT const& inv = gpTempDealersInventory[slot_num];
2067 			Assert(inv.sSpecialItemElement != -1);
2068 			buf = BuildDoneWhenTimeString(gbSelectedArmsDealerID, item_idx, inv.sSpecialItemElement);
2069 			DrawTextToScreen(buf, x + SKI_INV_PRICE_OFFSET_X, y + SKI_INV_PRICE_OFFSET_Y, SKI_INV_SLOT_WIDTH, SKI_ITEM_DESC_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
2070 			owner = inv.ubIdOfMercWhoOwnsTheItem;
2071 		}
2072 	}
2073 	else // Dealer's offer area
2074 	{
2075 		const DealerModel* dealer_info = SelectedArmsDealer();
2076 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
2077 		{
2078 			// The dealer repairs, there is an item here, therefore display the item's owner's face
2079 			owner     = ArmsDealerOfferArea[slot_num].ubIdOfMercWhoOwnsTheItem;
2080 			item_cost = CalculateObjectItemRepairCost(gbSelectedArmsDealerID, &item_o);
2081 		}
2082 		else
2083 		{
2084 			item_cost = CalcShopKeeperItemPrice(DEALER_SELLING, FALSE, item_idx, dealer_info->sellingPrice, &item_o);
2085 		}
2086 	}
2087 
2088 	if (item_cost != 0)
2089 	{
2090 		// Display the item's price
2091 		DrawTextToScreen(SPrintMoney(item_cost), x + SKI_INV_PRICE_OFFSET_X, y + SKI_INV_PRICE_OFFSET_Y, SKI_INV_SLOT_WIDTH, SKI_ITEM_DESC_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
2092 	}
2093 
2094 	// If the there is more then 1 or if the item is stackable and some of it has been bought and only 1 remains
2095 	if (item_o.ubNumberOfObjects > 1 || (
2096 		item_o.ubNumberOfObjects == 1      &&
2097 		DealerItemIsSafeToStack(item_idx)  &&
2098 		item_area == ARMS_DEALER_INVENTORY &&
2099 		gpTempDealersInventory[slot_num].uiFlags & ARMS_INV_ITEM_SELECTED))
2100 	{
2101 		buf = ST::format("x{}", item_o.ubNumberOfObjects);
2102 		DrawTextToScreen(buf, x + SKI_ITEM_NUMBER_TEXT_OFFSET_X, y + SKI_ITEM_NUMBER_TEXT_OFFSET_Y, SKI_ITEM_NUMBER_TEXT_WIDTH, SKIT_NUMBER_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, RIGHT_JUSTIFIED);
2103 	}
2104 
2105 	if (owner != NO_PROFILE)
2106 	{
2107 		// Display the face
2108 		INT8 const face_slot = GetSlotNumberForMerc(owner);
2109 		if (face_slot != -1)
2110 		{ // Still in player's employ
2111 			BltVideoObject(FRAME_BUFFER, guiSmallSoldiersFace[face_slot], 0, x + SKI_SMALL_FACE_OFFSET_X, y);
2112 		}
2113 	}
2114 
2115 	if (ItemHasAttachments(item_o))
2116 	{
2117 		// Display the '*' in the bottom right corner of the square
2118 		UINT8 attachmentHintColor = GetAttachmentHintColor(&item_o);
2119 		DrawTextToScreen("*", x + SKI_ATTACHMENT_SYMBOL_X_OFFSET, y + SKI_ATTACHMENT_SYMBOL_Y_OFFSET, 0, TINYFONT1, attachmentHintColor, FONT_MCOLOR_BLACK, LEFT_JUSTIFIED);
2120 	}
2121 
2122 	{
2123 		// Display 'JAMMED' or 'REPAIRED', if appropriate
2124 		ST::string overlay_text =
2125 			item_o.bGunAmmoStatus < 0 ? ST::string(TacticalStr[JAMMED_ITEM_STR]) :
2126 			print_repaired            ? ST::string(SKI_Text[SKI_TEXT_REPAIRED]) :
2127 			ST::null;
2128 		if (!overlay_text.empty())
2129 		{
2130 			INT16 cen_x;
2131 			INT16 cen_y;
2132 			FindFontCenterCoordinates(x, y, SKI_INV_SLOT_WIDTH, SKI_INV_HEIGHT, overlay_text, TINYFONT1, &cen_x, &cen_y);
2133 			DrawTextToScreen(overlay_text, cen_x, cen_y, SKI_INV_SLOT_WIDTH, TINYFONT1, FONT_RED, FONT_MCOLOR_BLACK, LEFT_JUSTIFIED);
2134 		}
2135 	}
2136 
2137 	if (hatched_out) HatchOutInvSlot(x, y);
2138 
2139 	InvalidateRegion(x, y, x + SKI_INV_SLOT_WIDTH, y + SKI_INV_SLOT_HEIGHT);
2140 
2141 	return item_cost;
2142 }
2143 
2144 
RepairmanItemQsortCompare(void const * pArg1,void const * pArg2)2145 static int RepairmanItemQsortCompare(void const* pArg1, void const* pArg2)
2146 {
2147 	INVENTORY_IN_SLOT const& inv_slot1 = *static_cast<INVENTORY_IN_SLOT const*>(pArg1);
2148 	INVENTORY_IN_SLOT const& inv_slot2 = *static_cast<INVENTORY_IN_SLOT const*>(pArg2);
2149 
2150 	Assert(inv_slot1.sSpecialItemElement != -1);
2151 	Assert(inv_slot2.sSpecialItemElement != -1);
2152 
2153 	DEALER_ITEM_HEADER const (& dih)[MAXITEMS] = gArmsDealersInventory[gbSelectedArmsDealerID];
2154 	UINT32             const repair_time1      = dih[inv_slot1.sItemIndex].SpecialItem[inv_slot1.sSpecialItemElement].uiRepairDoneTime;
2155 	UINT32             const repair_time2      = dih[inv_slot2.sItemIndex].SpecialItem[inv_slot2.sSpecialItemElement].uiRepairDoneTime;
2156 
2157 	// lower reapir time first
2158 	return repair_time1 < repair_time2 ? -1 :
2159 		repair_time1 > repair_time2 ?  1 :
2160 		0;
2161 }
2162 
2163 
2164 static void AddItemsToTempDealerInventory(UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, INT16 sSpecialItemElement, UINT8 ubHowMany, UINT8 ubOwner);
2165 static BOOLEAN RepairIsDone(UINT16 usItemIndex, UINT8 ubElement);
2166 
2167 
DetermineArmsDealersSellingInventory(void)2168 static void DetermineArmsDealersSellingInventory(void)
2169 {
2170 	UINT16  usItemIndex;
2171 	UINT8   ubElement;
2172 	DEALER_SPECIAL_ITEM *pSpecialItem;
2173 	BOOLEAN fAddSpecialItem;
2174 	SPECIAL_ITEM_INFO SpclItemInfo;
2175 
2176 	//if there is an old inventory, delete it
2177 	if( gpTempDealersInventory )
2178 	{
2179 		delete[] gpTempDealersInventory;
2180 		gpTempDealersInventory = NULL;
2181 	}
2182 
2183 	//allocate memory to hold the inventory in memory
2184 	gpTempDealersInventory = new INVENTORY_IN_SLOT[gSelectArmsDealerInfo.uiNumDistinctInventoryItems]{};
2185 	guiNextFreeInvSlot     = 0;
2186 
2187 	//loop through the dealer's permanent inventory items, adding them all to the temp inventory list
2188 	for( usItemIndex=1; usItemIndex<MAXITEMS; usItemIndex++)
2189 	{
2190 		//if the arms dealer has some of the inventory
2191 		if( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].ubTotalItems > 0)
2192 		{
2193 			// if there are any items in perfect condition
2194 			if( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].ubPerfectItems > 0 )
2195 			{
2196 				// create just ONE dealer inventory box for them all.
2197 				// create item info describing a perfect item
2198 				SetSpecialItemInfoToDefaults( &SpclItemInfo );
2199 				// no special element index - it's "perfect"
2200 				AddItemsToTempDealerInventory(usItemIndex, &SpclItemInfo, -1, gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].ubPerfectItems, NO_PROFILE );
2201 			}
2202 
2203 			// add all active special items
2204 			Assert(gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem.size() <= UINT8_MAX);
2205 			for (ubElement = 0; ubElement < static_cast<UINT8>(gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem.size()); ubElement++)
2206 			{
2207 				pSpecialItem = &(gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem[ ubElement ]);
2208 
2209 				if ( pSpecialItem->fActive )
2210 				{
2211 					fAddSpecialItem = TRUE;
2212 
2213 					//if the item is in for repairs
2214 					if( pSpecialItem->Info.bItemCondition < 0 )
2215 					{
2216 						//if the repairs are done
2217 						if( pSpecialItem->uiRepairDoneTime <= GetWorldTotalMin() )
2218 						{
2219 							if (RepairIsDone( usItemIndex, ubElement ))
2220 							{
2221 								// don't add it here, it was put in the player's area
2222 								fAddSpecialItem = FALSE;
2223 							}
2224 							else
2225 							{
2226 								gpTempDealersInventory[ guiNextFreeInvSlot ].uiFlags |= ARMS_INV_ITEM_REPAIRED;
2227 							}
2228 						}
2229 						else
2230 						{
2231 							gpTempDealersInventory[ guiNextFreeInvSlot ].uiFlags |= ARMS_INV_ITEM_NOT_REPAIRED_YET;
2232 						}
2233 					}
2234 
2235 					if ( fAddSpecialItem )
2236 					{
2237 						UINT8 ubOwner;
2238 
2239 						if (!DoesDealerDoRepairs(gbSelectedArmsDealerID))
2240 						{
2241 							// no merc is the owner
2242 							ubOwner = NO_PROFILE;
2243 						}
2244 						else
2245 						{
2246 							// retain owner so we can display this
2247 							ubOwner = pSpecialItem->ubOwnerProfileId;
2248 						}
2249 
2250 						AddItemsToTempDealerInventory( usItemIndex, &(pSpecialItem->Info), ubElement, 1, ubOwner );
2251 					}
2252 				}
2253 			}
2254 		}
2255 	}
2256 
2257 	// if more than one item is in inventory
2258 	if ( guiNextFreeInvSlot > 1 )
2259 	{
2260 		// repairmen sort differently from merchants
2261 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
2262 		{
2263 			// sort this list by object category, and by ascending price within each category
2264 			qsort( gpTempDealersInventory, guiNextFreeInvSlot, sizeof( INVENTORY_IN_SLOT ), RepairmanItemQsortCompare );
2265 		}
2266 		else
2267 		{
2268 			// sort this list by object category, and by ascending price within each category
2269 			qsort( gpTempDealersInventory, guiNextFreeInvSlot, sizeof( INVENTORY_IN_SLOT ), ArmsDealerItemQsortCompare );
2270 		}
2271 	}
2272 }
2273 
2274 
2275 static void StoreObjectsInNextFreeDealerInvSlot(UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, INT16 sSpecialItemElement, UINT8 ubHowMany, UINT8 ubOwner);
2276 
2277 
AddItemsToTempDealerInventory(UINT16 usItemIndex,SPECIAL_ITEM_INFO * pSpclItemInfo,INT16 sSpecialItemElement,UINT8 ubHowMany,UINT8 ubOwner)2278 static void AddItemsToTempDealerInventory(UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, INT16 sSpecialItemElement, UINT8 ubHowMany, UINT8 ubOwner)
2279 {
2280 	UINT8 ubCnt;
2281 
2282 
2283 	Assert( ubHowMany > 0 );
2284 	Assert( pSpclItemInfo != NULL );
2285 
2286 
2287 	// if there's just one of them
2288 	if ( ubHowMany == 1 )
2289 	{
2290 		// it gets its own box, obviously
2291 		StoreObjectsInNextFreeDealerInvSlot( usItemIndex, pSpclItemInfo, sSpecialItemElement, 1, ubOwner );
2292 	}
2293 	else // more than one
2294 	{
2295 		// if the items can be stacked
2296 		// NOTE: This test must match the one inside CountDistinctItemsInArmsDealersInventory() exactly!
2297 		if ( DealerItemIsSafeToStack( usItemIndex ) )
2298 		{
2299 			// then we can store them all together in the same box safely, even if there's more than MAX_OBJECTS_PER_SLOT
2300 			StoreObjectsInNextFreeDealerInvSlot( usItemIndex, pSpclItemInfo, sSpecialItemElement, ubHowMany, ubOwner );
2301 		}
2302 		else
2303 		{
2304 			// non-stacking items must be stored in one / box , because each may have unique fields besides bStatus[]
2305 			// Example: guns all have ammo, ammo type, etc.  We need these uniquely represented for pricing & manipulation
2306 			for ( ubCnt = 0; ubCnt < ubHowMany; ubCnt++ )
2307 			{
2308 				StoreObjectsInNextFreeDealerInvSlot( usItemIndex, pSpclItemInfo, sSpecialItemElement, 1, ubOwner );
2309 			}
2310 		}
2311 	}
2312 }
2313 
2314 
StoreObjectsInNextFreeDealerInvSlot(UINT16 usItemIndex,SPECIAL_ITEM_INFO * pSpclItemInfo,INT16 sSpecialItemElement,UINT8 ubHowMany,UINT8 ubOwner)2315 static void StoreObjectsInNextFreeDealerInvSlot(UINT16 usItemIndex, SPECIAL_ITEM_INFO* pSpclItemInfo, INT16 sSpecialItemElement, UINT8 ubHowMany, UINT8 ubOwner)
2316 {
2317 	INVENTORY_IN_SLOT *pDealerInvSlot;
2318 
2319 
2320 	// make sure we have the room (memory allocated for it)
2321 	Assert( guiNextFreeInvSlot < gSelectArmsDealerInfo.uiNumDistinctInventoryItems );
2322 
2323 	pDealerInvSlot = &(gpTempDealersInventory[ guiNextFreeInvSlot ]);
2324 	guiNextFreeInvSlot++;
2325 
2326 	pDealerInvSlot->fActive = TRUE;
2327 	pDealerInvSlot->sItemIndex = usItemIndex;
2328 	pDealerInvSlot->sSpecialItemElement = sSpecialItemElement;
2329 	pDealerInvSlot->ubIdOfMercWhoOwnsTheItem = ubOwner;
2330 	pDealerInvSlot->bSlotIdInOtherLocation = -1;
2331 
2332 	// Create the item object ( with no more than MAX_OBJECTS_PER_SLOT )
2333 	// can't use the real #, because CreateItems() will blindly set the bStatus for however many we tell it, beyond 8
2334 	MakeObjectOutOfDealerItems( usItemIndex, pSpclItemInfo, &(pDealerInvSlot->ItemObject), ( UINT8 ) MIN( ubHowMany, MAX_OBJECTS_PER_SLOT ) );
2335 
2336 	if ( ubHowMany > MAX_OBJECTS_PER_SLOT )
2337 	{
2338 		// HACK:  Override ItemObject->ubNumberOfObjects (1-8) with the REAL # of items in this box.
2339 		// Note that this makes it an illegal OBJECTTYPE, since there ubHowMany can easily be more than MAX_OBJECTS_PER_SLOT,
2340 		// so there's no room to store the status of all of them one.  But we only so this for perfect items, so
2341 		// we don't care, it works & saves us a lot hassles here.  Just be careful using the damn things!!!  You can't just
2342 		// pass them off the most functions in Items.C(), use ShopkeeperAutoPlaceObject() and ShopkeeperAddItemToPool() instead.
2343 		pDealerInvSlot->ItemObject.ubNumberOfObjects = ubHowMany;
2344 	}
2345 }
2346 
2347 
2348 static INT8 AddItemToPlayersOfferArea(UINT8 ubProfileID, const INVENTORY_IN_SLOT* pInvSlot, INT8 bSlotIdInOtherLocation);
2349 
2350 
RepairIsDone(UINT16 usItemIndex,UINT8 ubElement)2351 static BOOLEAN RepairIsDone(UINT16 usItemIndex, UINT8 ubElement)
2352 {
2353 	INVENTORY_IN_SLOT RepairItem;
2354 	INT8  bSlotNum;
2355 	UINT8 ubCnt;
2356 
2357 
2358 	// make a new shopkeeper invslot item out of it
2359 	RepairItem = INVENTORY_IN_SLOT{};
2360 
2361 	RepairItem.fActive = TRUE;
2362 	RepairItem.sItemIndex = usItemIndex;
2363 
2364 	// set the owner of the item.  Slot is always -1 of course.
2365 	RepairItem.ubIdOfMercWhoOwnsTheItem = gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem[ ubElement ].ubOwnerProfileId;
2366 	RepairItem.bSlotIdInOtherLocation = -1;
2367 
2368 	// Create the item object
2369 	MakeObjectOutOfDealerItems( usItemIndex, &( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem[ ubElement ].Info ), &RepairItem.ItemObject, 1 );
2370 
2371 	if ( CanDealerRepairItem( gbSelectedArmsDealerID, RepairItem.ItemObject.usItem ) )
2372 	{
2373 		// make its condition 100%
2374 		RepairItem.ItemObject.bStatus[ 0 ] = 100;
2375 	}
2376 
2377 	// max condition of all permanent attachments on it
2378 	for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
2379 	{
2380 		if ( RepairItem.ItemObject.usAttachItem[ ubCnt ] != NONE )
2381 		{
2382 			/* ARM: Can now repair with removeable attachments still attached...
2383 			// If the attachment is a permanent one
2384 			if ( GCM->getItem(RepairItem.ItemObject.usAttachItem[ ubCnt ])->getFlags() & ITEM_INSEPARABLE )*/
2385 			if ( CanDealerRepairItem( gbSelectedArmsDealerID, RepairItem.ItemObject.usAttachItem[ ubCnt ] ) )
2386 			{
2387 				// fix it up
2388 				RepairItem.ItemObject.bAttachStatus[ ubCnt ] = 100;
2389 			}
2390 		}
2391 	}
2392 
2393 	// if the item is imprinted (by anyone, even player's mercs), and it's the elctronics guy repairing it
2394 	if (
2395 		/*( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ].SpecialItem[ ubElement ].Info.ubImprintID == (NO_PROFILE + 1) ) && */
2396 		GetDealer(gbSelectedArmsDealerID)->hasFlag(ArmsDealerFlag::REPAIRS_ELECTRONICS))
2397 	{
2398 		// then reset the imprinting!
2399 		RepairItem.ItemObject.ubImprintID = NO_PROFILE;
2400 	}
2401 
2402 	//try to add the item to the players offer area
2403 	bSlotNum = AddItemToPlayersOfferArea( RepairItem.ubIdOfMercWhoOwnsTheItem, &RepairItem, -1 );
2404 	// if there wasn't room for it
2405 	if( bSlotNum == -1 )
2406 	{
2407 		// then we have to treat it like it's not done yet!
2408 		return(FALSE);
2409 	}
2410 
2411 	PlayersOfferArea[ bSlotNum ].uiFlags |= ARMS_INV_ITEM_REPAIRED;
2412 
2413 	// Remove the repaired item from the dealer's permanent inventory list
2414 	RemoveSpecialItemFromArmsDealerInventoryAtElement( gbSelectedArmsDealerID, usItemIndex, ubElement );
2415 
2416 	// one less slot is needed.  Don't bother ReMemAllocating, though
2417 	gSelectArmsDealerInfo.uiNumDistinctInventoryItems--;
2418 
2419 	// there was room to add it to player's area
2420 	return(TRUE);
2421 }
2422 
2423 
DrawHatchOnInventory(SGPVSurface * const uiSurface,const UINT16 usPosX,const UINT16 usPosY,const UINT16 usWidth,const UINT16 usHeight)2424 void DrawHatchOnInventory(SGPVSurface* const uiSurface, const UINT16 usPosX, const UINT16 usPosY, const UINT16 usWidth, const UINT16 usHeight)
2425 {
2426 	SGPRect ClipRect;
2427 	ClipRect.iLeft   = usPosX;
2428 	ClipRect.iRight  = usPosX + usWidth  - 1;
2429 	ClipRect.iTop    = usPosY;
2430 	ClipRect.iBottom = usPosY + usHeight - 1;
2431 
2432 	SGPVSurface::Lock l(uiSurface);
2433 	Blt16BPPBufferHatchRect(l.Buffer<UINT16>(), l.Pitch(), &ClipRect);
2434 }
2435 
2436 
2437 static FLOAT ItemConditionModifier(UINT16 usItemIndex, INT8 bStatus);
2438 
2439 
CalcShopKeeperItemPrice(BOOLEAN fDealerSelling,BOOLEAN fUnitPriceOnly,UINT16 usItemID,FLOAT dModifier,const OBJECTTYPE * pItemObject)2440 static UINT32 CalcShopKeeperItemPrice(BOOLEAN fDealerSelling, BOOLEAN fUnitPriceOnly, UINT16 usItemID, FLOAT dModifier, const OBJECTTYPE* pItemObject)
2441 {
2442 	UINT8  ubCnt;
2443 	UINT32 uiUnitPrice = 0;
2444 	UINT32 uiTotalPrice = 0;
2445 	UINT8  ubItemsToCount = 0;
2446 	UINT8  ubItemsNotCounted = 0;
2447 	UINT32 uiDiscountValue;
2448 	//UINT32 uiDifFrom10 = 0;
2449 
2450 
2451 	// add up value of the main item(s), exact procedure depends on its item class
2452 	switch ( GCM->getItem(usItemID)->getItemClass() )
2453 	{
2454 		case IC_GUN:
2455 			// add value of the gun
2456 			uiUnitPrice += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, usItemID, fDealerSelling ) *
2457 					ItemConditionModifier(usItemID, pItemObject->bGunStatus) *
2458 					dModifier );
2459 
2460 			// if any ammo is loaded
2461 			if( pItemObject->usGunAmmoItem != NONE)
2462 			{
2463 				// if it's regular ammo
2464 				if( GCM->getItem(pItemObject->usGunAmmoItem)->getItemClass() == IC_AMMO )
2465 				{
2466 					// add value of its remaining ammo
2467 					uiUnitPrice += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, pItemObject->usGunAmmoItem, fDealerSelling ) *
2468 								ItemConditionModifier(pItemObject->usGunAmmoItem, pItemObject->ubGunShotsLeft) *
2469 								dModifier );
2470 				}
2471 				else // assume it's attached ammo (mortar shells, grenades)
2472 				{
2473 					// add its value (uses normal status 0-100)
2474 					uiUnitPrice += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, pItemObject->usGunAmmoItem, fDealerSelling ) *
2475 							ItemConditionModifier(pItemObject->usGunAmmoItem, pItemObject->bGunAmmoStatus) *
2476 							dModifier );
2477 				}
2478 			}
2479 
2480 			// if multiple guns are stacked, we've only counted the first one
2481 			ubItemsNotCounted = pItemObject->ubNumberOfObjects - 1;
2482 			break;
2483 
2484 		case IC_AMMO:
2485 		default:
2486 			// this must handle overloaded objects from dealer boxes!
2487 			if ( pItemObject->ubNumberOfObjects <= MAX_OBJECTS_PER_SLOT )
2488 			{
2489 				// legal amount, count them all normally (statuses could be different)
2490 				ubItemsToCount = pItemObject->ubNumberOfObjects;
2491 				ubItemsNotCounted = 0;
2492 				// in this situation, uiUnitPrice will actually be the sum of the values of ALL the multiple objects
2493 			}
2494 			else
2495 			{
2496 				// overloaded amount, count just the first, the others must all be identical
2497 				ubItemsToCount = 1;
2498 				ubItemsNotCounted = pItemObject->ubNumberOfObjects - 1;
2499 			}
2500 
2501 			// add the value of each magazine (multiple mags may have vastly different #bullets left)
2502 			for (ubCnt = 0; ubCnt < ubItemsToCount; ubCnt++ )
2503 			{
2504 				// for bullets, ItemConditionModifier will convert the #ShotsLeft into a percentage
2505 				uiUnitPrice += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, usItemID, fDealerSelling ) *
2506 							ItemConditionModifier(usItemID, pItemObject->bStatus[ ubCnt ]) *
2507 							dModifier );
2508 
2509 				if ( fUnitPriceOnly )
2510 				{
2511 					// want price for only one of them.  All statuses must be the same in order to use this flag!
2512 					break;
2513 				}
2514 			}
2515 			break;
2516 	}
2517 
2518 
2519 	// loop through any attachments and add in their price
2520 	for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++)
2521 	{
2522 		if( pItemObject->usAttachItem[ ubCnt ] != NONE )
2523 		{
2524 			// add value of this particular attachment
2525 			uiUnitPrice += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, pItemObject->usAttachItem[ ubCnt ], fDealerSelling ) *
2526 					ItemConditionModifier(pItemObject->usAttachItem[ ubCnt ], pItemObject->bAttachStatus[ ubCnt ]) *
2527 					dModifier );
2528 		}
2529 	}
2530 
2531 
2532 	// if Flo is doing the dealin' and wheelin'
2533 	if ( gpSMCurrentMerc->ubProfile == FLO )
2534 	{
2535 		// if it's a GUN or AMMO (but not Launchers, and all attachments and payload is included)
2536 		switch ( GCM->getItem(usItemID)->getItemClass() )
2537 		{
2538 			// start components of IC_WEAPON:
2539 			case IC_GUN:
2540 			case IC_BLADE:
2541 			case IC_THROWING_KNIFE:
2542 			case IC_LAUNCHER:
2543 			// end components of IC_WEAPON
2544 			case IC_AMMO:
2545 				uiDiscountValue = ( uiUnitPrice * FLO_DISCOUNT_PERCENTAGE ) / 100;
2546 
2547 				// she gets a discount!  Read her M.E.R.C. profile to understand why
2548 				if ( fDealerSelling )
2549 				{
2550 					// she buys for less...
2551 					uiUnitPrice -= uiDiscountValue;
2552 				}
2553 				else
2554 				{
2555 					// and sells for more!
2556 					uiUnitPrice += uiDiscountValue;
2557 				}
2558 				break;
2559 		}
2560 	}
2561 
2562 
2563 	// if it's the dealer selling this, make sure the item is worth at least $1
2564 	// if he is buying this from a player, then we allow a value of 0, since that has a special "worthless" quote #18
2565 	if( fDealerSelling && ( uiUnitPrice == 0 ) )
2566 	{
2567 		uiUnitPrice = 1;
2568 	}
2569 
2570 	/*
2571 	//if the price is not diviseble by 10, make it so
2572 	uiDifFrom10 = 10 - uiUnitPrice % 10;
2573 	if( uiDifFrom10 != 0 && uiDifFrom10 != 10 )
2574 	{
2575 		uiUnitPrice += uiDifFrom10;
2576 	}*/
2577 
2578 	// we're always count the first one
2579 	uiTotalPrice = uiUnitPrice;
2580 
2581 	// if NOT pricing just one
2582 	if ( !fUnitPriceOnly )
2583 	{
2584 		// add value of all that weren't already counted
2585 		uiTotalPrice += ( ubItemsNotCounted * uiUnitPrice );
2586 	}
2587 
2588 	return( uiTotalPrice );
2589 }
2590 
2591 
ItemConditionModifier(UINT16 usItemIndex,INT8 bStatus)2592 static FLOAT ItemConditionModifier(UINT16 usItemIndex, INT8 bStatus)
2593 {
2594 	FLOAT dConditionModifier = 1.0f;
2595 
2596 	//if the item is ammo, the condition modifier is based on how many shots are left
2597 	if( GCM->getItem(usItemIndex)->getItemClass() == IC_AMMO )
2598 	{
2599 		// # bullets left / max magazine capacity
2600 		dConditionModifier = bStatus / ((FLOAT) GCM->getItem(usItemIndex)->asAmmo()->capacity? (FLOAT) GCM->getItem(usItemIndex)->asAmmo()->capacity: 1);
2601 	}
2602 	else	// non-ammo
2603 	{
2604 		// handle non-ammo payloads in jammed guns as if they weren't
2605 		if ( bStatus < 0 )
2606 		{
2607 			bStatus *= -1;
2608 		}
2609 
2610 		// an item at 100% is worth full price...
2611 
2612 		if ( GCM->getItem(usItemIndex)->getFlags() & ITEM_REPAIRABLE )
2613 		{
2614 			// a REPAIRABLE item at 0% is still worth 50% of its full price, not 0%
2615 			dConditionModifier = 0.5f + ( bStatus / (FLOAT)200 );
2616 		}
2617 		else
2618 		{
2619 			// an UNREPAIRABLE item is worth precisely its condition percentage
2620 			dConditionModifier = bStatus / (FLOAT)100;
2621 		}
2622 	}
2623 
2624 	return( dConditionModifier );
2625 }
2626 
2627 
DisplayArmsDealerOfferArea(void)2628 static void DisplayArmsDealerOfferArea(void)
2629 {
2630 	INT16   sCnt, sCount;
2631 	UINT32  uiTotalCost;
2632 	UINT16  usPosX, usPosY;
2633 	BOOLEAN fDisplayHatchOnItem;
2634 
2635 
2636 	usPosX = SKI_ARMS_DEALERS_TRADING_INV_X;
2637 	usPosY = SKI_ARMS_DEALERS_TRADING_INV_Y;
2638 
2639 	if( gubSkiDirtyLevel != SKI_DIRTY_LEVEL0 )
2640 	{
2641 		//Restore the area before blitting the new data on
2642 		RestoreExternBackgroundRect( usPosX, usPosY, SKI_ARMS_DEALERS_TRADING_INV_WIDTH, SKI_ARMS_DEALERS_TRADING_INV_HEIGHT );
2643 	}
2644 
2645 	sCount = 0;
2646 
2647 	//Display all the items that are in the Arms traders offered area
2648 	uiTotalCost = 0;
2649 	for( sCnt=0; sCnt<SKI_NUM_TRADING_INV_SLOTS; sCnt++)
2650 	{
2651 		const INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[sCnt];
2652 		if (a->fActive)
2653 		{
2654 			// hatch it out if region is disabled
2655 			if ( !( gDealersOfferSlotsMouseRegions[ sCnt ].uiFlags & MSYS_REGION_ENABLED ) )
2656 			{
2657 				fDisplayHatchOnItem = TRUE;
2658 			}
2659 			else if (a->uiFlags & ARMS_INV_ITEM_SELECTED)
2660 			{
2661 				fDisplayHatchOnItem = TRUE;
2662 			}
2663 			else
2664 				fDisplayHatchOnItem = FALSE;
2665 
2666 			// Display the inventory slot
2667 			uiTotalCost += DisplayInvSlot(sCnt, a->sItemIndex, usPosX, usPosY, a->ItemObject, fDisplayHatchOnItem, ARMS_DEALER_OFFER_AREA);
2668 		}
2669 		else	// empty
2670 		{
2671 			// hatch it out if region is disabled
2672 			if ( !( gDealersOfferSlotsMouseRegions[ sCnt ].uiFlags & MSYS_REGION_ENABLED ) )
2673 			{
2674 				HatchOutInvSlot( usPosX, usPosY );
2675 			}
2676 		}
2677 
2678 		usPosX += SKI_INV_OFFSET_X;
2679 
2680 		sCount++;
2681 
2682 		//if we are on to the next row
2683 		if( !( sCount % SKI_NUM_TRADING_INV_COLS ) )
2684 		{
2685 			usPosX = SKI_ARMS_DEALERS_TRADING_INV_X;
2686 			usPosY += SKI_INV_OFFSET_Y;
2687 		}
2688 	}
2689 
2690 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
2691 	{
2692 		//
2693 		//Display the Total cost text and number
2694 		//
2695 
2696 		//Restore the previous area
2697 		RestoreExternBackgroundRect( SKI_ARMS_DEALER_TOTAL_COST_X, SKI_ARMS_DEALER_TOTAL_COST_Y, SKI_ARMS_DEALER_TOTAL_COST_WIDTH, SKI_ARMS_DEALER_TOTAL_COST_HEIGHT );
2698 
2699 		//Display the total cost text
2700 		DrawTextToScreen(SPrintMoney(uiTotalCost), SKI_ARMS_DEALER_TOTAL_COST_X, SKI_ARMS_DEALER_TOTAL_COST_Y + 5, SKI_INV_SLOT_WIDTH, SKI_LABEL_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
2701 	}
2702 }
2703 
2704 
AddItemToArmsDealerOfferArea(const INVENTORY_IN_SLOT * pInvSlot,INT8 bSlotIdInOtherLocation)2705 static INT8 AddItemToArmsDealerOfferArea(const INVENTORY_IN_SLOT* pInvSlot, INT8 bSlotIdInOtherLocation)
2706 {
2707 	INT8 bCnt;
2708 
2709 	for( bCnt=0; bCnt<SKI_NUM_TRADING_INV_SLOTS; bCnt++)
2710 	{
2711 		INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[bCnt];
2712 		//if there are no items here, copy the data in
2713 		if (!a->fActive)
2714 		{
2715 			//Copy the inventory items
2716 			*a = *pInvSlot;
2717 
2718 			//if the shift key is being pressed, add them all
2719 			if (_KeyDown(SHIFT))
2720 			{
2721 				a->ItemObject.ubNumberOfObjects = pInvSlot->ItemObject.ubNumberOfObjects;
2722 			}
2723 			else if( pInvSlot->ItemObject.ubNumberOfObjects > 1 )
2724 			{
2725 				//If there was more then 1 item, reduce it to only 1 item moved
2726 				a->ItemObject.ubNumberOfObjects = 1;
2727 			}
2728 
2729 			//Remember where the item came from
2730 			a->bSlotIdInOtherLocation   = bSlotIdInOtherLocation;
2731 			a->ubIdOfMercWhoOwnsTheItem = pInvSlot->ubIdOfMercWhoOwnsTheItem;
2732 			a->fActive                  = TRUE;
2733 
2734 			SetSkiRegionHelpText(    a, &gDealersOfferSlotsMouseRegions[bCnt],          ARMS_DEALER_OFFER_AREA);
2735 			SetSkiFaceRegionHelpText(a, &gDealersOfferSlotsSmallFaceMouseRegions[bCnt], ARMS_DEALER_OFFER_AREA);
2736 
2737 			gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
2738 			return( bCnt );
2739 		}
2740 	}
2741 
2742 	return( -1 );
2743 }
2744 
2745 
2746 static void ClearArmsDealerOfferSlot(INT32 ubSlotToClear);
2747 
2748 
RemoveItemFromArmsDealerOfferArea(INT8 bSlotId,BOOLEAN fKeepItem)2749 static BOOLEAN RemoveItemFromArmsDealerOfferArea(INT8 bSlotId, BOOLEAN fKeepItem)
2750 {
2751 	//UINT16 usCnt;
2752 
2753 	//Loop through all the slot to see if the requested one is here
2754 	//for( usCnt=0; usCnt<SKI_NUM_TRADING_INV_SLOTS; usCnt++)
2755 	{
2756 		const INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[bSlotId];
2757 		//if this is the requested slot
2758 		if (a->fActive)//bSlotIdInOtherLocation == bSlotIdInOtherLocation )
2759 		{
2760 			//if there are more then 1 item
2761 			//if( ArmsDealerOfferArea[ usCnt ].ItemObject.ubNumberOfObjects > 1 )
2762 			if( fKeepItem )
2763 			{
2764 				gpTempDealersInventory[a->bSlotIdInOtherLocation].ItemObject.ubNumberOfObjects += a->ItemObject.ubNumberOfObjects;
2765 			}
2766 
2767 			//Clear the flag that hatches out the item
2768 			gpTempDealersInventory[a->bSlotIdInOtherLocation].uiFlags &= ~ARMS_INV_ITEM_SELECTED;
2769 
2770 			ClearArmsDealerOfferSlot( bSlotId );
2771 
2772 			return( TRUE );
2773 		}
2774 	}
2775 
2776 	return( FALSE );
2777 }
2778 
2779 
2780 static ST::string BuildItemHelpTextString(const INVENTORY_IN_SLOT* pInv, UINT8 ubScreenArea);
2781 
2782 
SetSkiRegionHelpText(const INVENTORY_IN_SLOT * pInv,MOUSE_REGION * pRegion,UINT8 ubScreenArea)2783 static void SetSkiRegionHelpText(const INVENTORY_IN_SLOT* pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea)
2784 {
2785 	Assert( pRegion );
2786 
2787 	ST::string zHelpText = BuildItemHelpTextString(pInv, ubScreenArea );
2788 	pRegion->SetFastHelpText(zHelpText);
2789 }
2790 
2791 
SetSkiFaceRegionHelpText(const INVENTORY_IN_SLOT * pInv,MOUSE_REGION * pRegion,UINT8 ubScreenArea)2792 static void SetSkiFaceRegionHelpText(const INVENTORY_IN_SLOT* pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea)
2793 {
2794 	ST::string zTempText;
2795 	ST::string zHelpText;
2796 
2797 	Assert( pRegion );
2798 
2799 	//if the item isn't NULL, and is owned by a merc
2800 	if( ( pInv != NULL ) && ( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) )
2801 	{
2802 		zTempText = BuildItemHelpTextString(pInv, ubScreenArea);
2803 		// add who owns it
2804 		zHelpText = ST::format("{}{} {}", gMercProfiles[ pInv->ubIdOfMercWhoOwnsTheItem ].zNickname, pMessageStrings[ MSG_DASH_S ], zTempText);
2805 	}
2806 	else
2807 	{
2808 		zHelpText = ST::null;
2809 	}
2810 	pRegion->SetFastHelpText(zHelpText);
2811 }
2812 
2813 
2814 static INVENTORY_IN_SLOT* GetPtrToOfferSlotWhereThisItemIs(UINT8 ubProfileID, INT8 bInvPocket);
2815 
2816 
AddItemToPlayersOfferArea(UINT8 ubProfileID,const INVENTORY_IN_SLOT * pInvSlot,INT8 bSlotIdInOtherLocation)2817 static INT8 AddItemToPlayersOfferArea(UINT8 ubProfileID, const INVENTORY_IN_SLOT* pInvSlot, INT8 bSlotIdInOtherLocation)
2818 {
2819 	INT8 bCnt;
2820 
2821 	//if we are to check for a previous slot
2822 	if( bSlotIdInOtherLocation != -1 )
2823 	{
2824 		//if the item has already been added somewhere. don't add it again
2825 		if( GetPtrToOfferSlotWhereThisItemIs( ubProfileID, bSlotIdInOtherLocation ) != NULL )
2826 			return( -1 );
2827 	}
2828 
2829 
2830 	//look for the first free slot
2831 	for( bCnt=0; bCnt<SKI_NUM_TRADING_INV_SLOTS; bCnt++)
2832 	{
2833 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bCnt];
2834 		//if there are no items here, copy the data in
2835 		if (!o->fActive)
2836 		{
2837 			*o = *pInvSlot;
2838 
2839 			o->fActive = TRUE;
2840 
2841 			//Specify the owner of the merc
2842 			o->ubIdOfMercWhoOwnsTheItem = ubProfileID;
2843 			o->bSlotIdInOtherLocation = bSlotIdInOtherLocation;
2844 
2845 			IfMercOwnedCopyItemToMercInv(o);
2846 
2847 			SetSkiRegionHelpText(    o, &gPlayersOfferSlotsMouseRegions[bCnt],          PLAYERS_OFFER_AREA);
2848 			SetSkiFaceRegionHelpText(o, &gPlayersOfferSlotsSmallFaceMouseRegions[bCnt], PLAYERS_OFFER_AREA);
2849 
2850 
2851 			//if the item we are adding is money
2852 			if( GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY )
2853 			{
2854 				//Since money is always evaluated
2855 				o->uiFlags     |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE;
2856 				o->uiItemPrice  = o->ItemObject.uiMoneyAmount;
2857 			}
2858 			gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
2859 
2860 			return( bCnt );
2861 		}
2862 	}
2863 
2864 	return( -1 );
2865 }
2866 
2867 
2868 static void ClearPlayersOfferSlot(INT32 ubSlotToClear);
2869 static BOOLEAN SKITryToReturnInvToOwnerOrCurrentMerc(INVENTORY_IN_SLOT* pInv);
2870 
2871 
RemoveItemFromPlayersOfferArea(INT8 bSlot)2872 static BOOLEAN RemoveItemFromPlayersOfferArea(INT8 bSlot)
2873 {
2874 	INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bSlot];
2875 	//if the item doesn't have a duplicate copy in its owner merc's inventory slot
2876 	if (o->bSlotIdInOtherLocation == -1 &&
2877 		!SKITryToReturnInvToOwnerOrCurrentMerc(o))
2878 	{
2879 		//failed to add item, inventory probably filled up or item is unowned and current merc ineligible
2880 		return( FALSE );
2881 	}
2882 
2883 	// Clear the contents
2884 	ClearPlayersOfferSlot( bSlot );
2885 
2886 	// Dirty
2887 	fInterfacePanelDirty = DIRTYLEVEL2;
2888 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
2889 	return( TRUE );
2890 }
2891 
2892 
2893 static UINT32 EvaluateInvSlot(INVENTORY_IN_SLOT* pInvSlot);
2894 static BOOLEAN WillShopKeeperRejectObjectsFromPlayer(INT8 bDealerId, INT8 bSlotId);
2895 
2896 
DisplayPlayersOfferArea(void)2897 static void DisplayPlayersOfferArea(void)
2898 {
2899 	INT16   sCnt, sCount;
2900 	UINT32  uiTotalCost;
2901 	UINT16  usPosX, usPosY;
2902 	BOOLEAN fDisplayHatchOnItem=FALSE;
2903 
2904 
2905 	usPosX = SKI_PLAYERS_TRADING_INV_X;
2906 	usPosY = SKI_PLAYERS_TRADING_INV_Y;
2907 
2908 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
2909 	{
2910 		//Restore the area before blitting the new data on
2911 		RestoreExternBackgroundRect( SKI_PLAYERS_TRADING_INV_X, SKI_PLAYERS_TRADING_INV_Y, SKI_PLAYERS_TRADING_INV_WIDTH, SKI_PLAYERS_TRADING_INV_HEIGHT );
2912 	}
2913 
2914 	sCount = 0;
2915 
2916 
2917 	//Display all the items that are in the Players offered area
2918 	uiTotalCost = 0;
2919 	for( sCnt=0; sCnt<SKI_NUM_TRADING_INV_SLOTS; sCnt++)
2920 	{
2921 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[sCnt];
2922 		if (o->fActive)
2923 		{
2924 			//if the item is money
2925 			if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
2926 			{
2927 				//get an updated status from the amount in the pocket
2928 				if (o->bSlotIdInOtherLocation != -1 && o->ubIdOfMercWhoOwnsTheItem != NO_PROFILE)
2929 				{
2930 					const SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(o->ubIdOfMercWhoOwnsTheItem);
2931 					Assert(s != NULL);
2932 					o->ItemObject.uiMoneyAmount = s->inv[o->bSlotIdInOtherLocation].uiMoneyAmount;
2933 					o->uiItemPrice = o->ItemObject.uiMoneyAmount;
2934 				}
2935 			}
2936 			else	// not money
2937 			{
2938 				//if non-repairman
2939 				if(!DoesDealerDoRepairs(gbSelectedArmsDealerID))
2940 				{
2941 					// don't evaluate anything he wouldn't buy!
2942 					if (!WillShopKeeperRejectObjectsFromPlayer(gbSelectedArmsDealerID, sCnt))
2943 					{
2944 						// skip purchased items!
2945 						if (!(o->uiFlags & ARMS_INV_JUST_PURCHASED))
2946 						{
2947 							// re-evaluate the value of the item (needed for Flo's discount handling)
2948 							EvaluateInvSlot(o);
2949 						}
2950 					}
2951 				}
2952 			}
2953 
2954 			// hatch it out if it hasn't been evaluated or just purchased
2955 			//fDisplayHatchOnItem = (o->uiFlags & (ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED | ARMS_INV_JUST_PURCHASED)) == 0;
2956 
2957 			// Display the inventory slot
2958 			DisplayInvSlot(sCnt, o->sItemIndex, usPosX, usPosY, o->ItemObject, fDisplayHatchOnItem, PLAYERS_OFFER_AREA);
2959 
2960 			if (o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE)
2961 			{
2962 				uiTotalCost += o->uiItemPrice;
2963 			}
2964 		}
2965 		usPosX += SKI_INV_OFFSET_X;
2966 
2967 		sCount++;
2968 
2969 		//if we are on to the next row
2970 		if( !( sCount % SKI_NUM_TRADING_INV_COLS ) )
2971 		{
2972 			usPosX = SKI_ARMS_DEALERS_TRADING_INV_X;
2973 			usPosY += SKI_INV_OFFSET_Y;
2974 		}
2975 	}
2976 
2977 	if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 )
2978 	{
2979 		//
2980 		//Display the Total value text and number
2981 		//
2982 
2983 		//Restore the previous area
2984 		RestoreExternBackgroundRect( SKI_PLAYERS_TOTAL_VALUE_X, SKI_PLAYERS_TOTAL_VALUE_Y, SKI_PLAYERS_TOTAL_VALUE_WIDTH, SKI_PLAYERS_TOTAL_VALUE_HEIGHT );
2985 
2986 		//Display the total cost text
2987 		DrawTextToScreen(SPrintMoney(uiTotalCost), SKI_PLAYERS_TOTAL_VALUE_X, SKI_PLAYERS_TOTAL_VALUE_Y + 5, SKI_INV_SLOT_WIDTH, SKI_LABEL_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, CENTER_JUSTIFIED);
2988 	}
2989 
2990 	CrossOutUnwantedItems( );
2991 }
2992 
2993 
GetPtrToOfferSlotWhereThisItemIs(UINT8 ubProfileID,INT8 bInvPocket)2994 static INVENTORY_IN_SLOT* GetPtrToOfferSlotWhereThisItemIs(UINT8 ubProfileID, INT8 bInvPocket)
2995 {
2996 	UINT8 ubCnt = 0;
2997 
2998 	for( ubCnt = 0; ubCnt < SKI_NUM_TRADING_INV_SLOTS; ubCnt++ )
2999 	{
3000 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubCnt];
3001 		if (o->bSlotIdInOtherLocation == bInvPocket &&
3002 			o->ubIdOfMercWhoOwnsTheItem == ubProfileID &&
3003 			o->ItemObject.ubNumberOfObjects != 0)
3004 		{
3005 			return o;
3006 		}
3007 
3008 		INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[ubCnt];
3009 		if (a->bSlotIdInOtherLocation == bInvPocket &&
3010 			a->ubIdOfMercWhoOwnsTheItem == ubProfileID &&
3011 			a->ItemObject.ubNumberOfObjects != 0)
3012 		{
3013 			return a;
3014 		}
3015 	}
3016 
3017 	// not found (perfectly valid result)
3018 	return( NULL );
3019 }
3020 
3021 
ShouldSoldierDisplayHatchOnItem(UINT8 ubProfileID,INT16 sSlotNum)3022 BOOLEAN ShouldSoldierDisplayHatchOnItem( UINT8 ubProfileID, INT16 sSlotNum )
3023 {
3024 	INVENTORY_IN_SLOT *pInvSlot = NULL;
3025 
3026 	pInvSlot = GetPtrToOfferSlotWhereThisItemIs( ubProfileID, ( INT8 ) sSlotNum );
3027 
3028 	if( pInvSlot == NULL )
3029 		// not found in either offer area
3030 		return( FALSE );
3031 	else
3032 		// found it, display it hatched out
3033 		return( TRUE );
3034 }
3035 
3036 
CalculateTotalArmsDealerCost(void)3037 static UINT32 CalculateTotalArmsDealerCost(void)
3038 {
3039 	UINT32 uiCnt;
3040 	UINT32 uiTotal=0;
3041 
3042 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
3043 	{
3044 		const INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[uiCnt];
3045 		if (a->fActive)
3046 		{
3047 			//if the dealer repairs
3048 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
3049 			{
3050 				uiTotal += CalculateObjectItemRepairCost(gbSelectedArmsDealerID, &a->ItemObject);
3051 			}
3052 			else
3053 			{
3054 				uiTotal += CalcShopKeeperItemPrice(DEALER_SELLING, FALSE, a->sItemIndex, SelectedArmsDealer()->sellingPrice, &a->ItemObject);
3055 			}
3056 		}
3057 	}
3058 	return( uiTotal );
3059 }
3060 
3061 
CalculateTotalPlayersValue(void)3062 static UINT32 CalculateTotalPlayersValue(void)
3063 {
3064 	UINT8  ubCnt;
3065 	UINT32 uiTotal = 0;
3066 
3067 	for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
3068 	{
3069 		const INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubCnt];
3070 		if (o->fActive && o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE)
3071 		{
3072 			//Calculate a price for the item
3073 			if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
3074 			{
3075 				uiTotal += o->ItemObject.uiMoneyAmount;
3076 			}
3077 			else
3078 			{
3079 				uiTotal += o->uiItemPrice;
3080 			}
3081 		}
3082 	}
3083 
3084 	return( uiTotal );
3085 }
3086 
3087 
3088 class DialogueEventSkipAFrame : public DialogueEvent
3089 {
3090 	public:
Execute()3091 		bool Execute() { return false; }
3092 };
3093 
3094 
3095 class DialogueEventShopkeeperLockTransactionButton : public DialogueEvent
3096 {
3097 	public:
DialogueEventShopkeeperLockTransactionButton(bool const lock)3098 		DialogueEventShopkeeperLockTransactionButton(bool const lock) : lock_(lock) {}
3099 
Execute()3100 		bool Execute()
3101 		{
3102 			if (guiCurrentScreen == SHOPKEEPER_SCREEN)
3103 			{
3104 				EnableButton(guiSKI_TransactionButton, !lock_);
3105 			}
3106 			return false;
3107 		}
3108 
3109 	private:
3110 		bool const lock_;
3111 };
3112 
3113 
3114 class DialogueEventShopkeeperMoney : public DialogueEvent
3115 {
3116 	public:
DialogueEventShopkeeperMoney(const ST::string & message,UINT32 money_amount,MessageBoxFlags flags,MSGBOX_CALLBACK callback)3117 		DialogueEventShopkeeperMoney(const ST::string& message, UINT32 money_amount, MessageBoxFlags flags, MSGBOX_CALLBACK callback) :
3118 			message_(message),
3119 			money_amount_(money_amount),
3120 			flags_(flags),
3121 			callback_(callback)
3122 		{}
3123 
Execute()3124 		bool Execute()
3125 		{
3126 			ST::string zMoney = SPrintMoney(money_amount_);
3127 			ST::string zText = st_format_printf(message_, zMoney);
3128 			DoSkiMessageBox(zText, SHOPKEEPER_SCREEN, flags_, callback_);
3129 			return false;
3130 		}
3131 
3132 	private:
3133 		ST::string      const message_;
3134 		UINT32          const money_amount_;
3135 		MessageBoxFlags const flags_;
3136 		MSGBOX_CALLBACK const callback_;
3137 };
3138 
3139 
3140 static UINT32 CalculateHowMuchMoneyIsInPlayersOfferArea();
3141 static UINT8 CountNumberOfItemsInTheArmsDealersOfferArea();
3142 static UINT8 CountNumberOfValuelessItemsInThePlayersOfferArea();
3143 static void DealerGetsBribed(UINT8 ubProfileId, UINT32 uiMoneyAmount);
3144 static void GivePlayerSomeChange(UINT32 uiAmount);
3145 static BOOLEAN IsMoneyTheOnlyItemInThePlayersOfferArea(void);
3146 static void MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea(void);
3147 static void MovePlayerOfferedItemsOfValueToArmsDealersInventory(void);
3148 static void MovePlayersItemsToBeRepairedToArmsDealersInventory(void);
3149 static BOOLEAN StartShopKeeperTalking(UINT16 usQuoteNum);
3150 
3151 
PerformTransaction(UINT32 uiMoneyFromPlayersAccount)3152 static void PerformTransaction(UINT32 uiMoneyFromPlayersAccount)
3153 {
3154 	UINT32 uiPlayersTotalMoneyValue = CalculateTotalPlayersValue() + uiMoneyFromPlayersAccount;
3155 	UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost();
3156 	UINT32 uiMoneyInPlayersOfferArea = CalculateHowMuchMoneyIsInPlayersOfferArea( );
3157 	INT32  iChangeToGiveToPlayer = 0;
3158 	UINT32 uiAvailablePlayerOfferSlots;
3159 
3160 
3161 	//if the player has already requested to leave, get out
3162 	if( gfUserHasRequestedToLeave )
3163 		return;
3164 
3165 
3166 	// handle bribing... if the player is giving the dealer money, without buying anything
3167 	if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 )
3168 	{
3169 		// accept/refuse money (varies by dealer according to ACCEPTS_GIFTS flag)
3170 		StartShopKeeperTalking( SK_QUOTES_DEALER_OFFERED_MONEY_AS_A_GIFT );
3171 
3172 		// if the arms dealer is the kind of person who accepts gifts
3173 		if( SelectedArmsDealer()->hasFlag(ArmsDealerFlag::ACCEPTS_GIFTS) )
3174 		{
3175 			//Move all the players evaluated items to the dealer (also adds it to dealer's cash)
3176 			MovePlayerOfferedItemsOfValueToArmsDealersInventory();
3177 
3178 			DealerGetsBribed( SelectedArmsDealer()->profileID, uiMoneyInPlayersOfferArea );
3179 		}
3180 	}
3181 	else // not a bribe
3182 	{
3183 		//if the dealer is not a repairman
3184 		if(!DoesDealerDoRepairs(gbSelectedArmsDealerID))
3185 		{
3186 			uiAvailablePlayerOfferSlots = ( SKI_NUM_TRADING_INV_SLOTS - CountNumberOfValuelessItemsInThePlayersOfferArea( ) );
3187 
3188 			// always reserve an empty slot for change.  We can't tell for sure whether player is getting change because that
3189 			// could depend on how many items he's trying to buy won't fit - he could only be getting change because of that!
3190 			if ( uiAvailablePlayerOfferSlots > 0 )
3191 			{
3192 				uiAvailablePlayerOfferSlots--;
3193 			}
3194 
3195 			//if there is NOT enough room in the players offer area
3196 			if( CountNumberOfItemsInTheArmsDealersOfferArea( ) > uiAvailablePlayerOfferSlots )
3197 			{
3198 				// tell player there's not enough room in the player's offer area
3199 				DoSkiMessageBox(SKI_Text[SKI_TEXT_NO_MORE_ROOM_IN_PLAYER_OFFER_AREA], SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, NULL);
3200 
3201 				return;
3202 			}
3203 		}
3204 
3205 		//if the player doesn't have enough money to pay for what he's buying
3206 		if( uiArmsDealersItemsCost > uiPlayersTotalMoneyValue )
3207 		{
3208 			//if the player doesn't have enough money in his account to pay the rest
3209 			if( uiArmsDealersItemsCost > uiPlayersTotalMoneyValue + LaptopSaveInfo.iCurrentBalance )
3210 			{
3211 				// tell player he can't possibly afford this
3212 				DialogueEvent::Add(new DialogueEventShopkeeperLockTransactionButton(true));
3213 				DialogueEvent::Add(new DialogueEventSkipAFrame());
3214 				UINT32 const amount = uiArmsDealersItemsCost - (LaptopSaveInfo.iCurrentBalance + uiPlayersTotalMoneyValue);
3215 				DialogueEvent::Add(new DialogueEventShopkeeperMoney(SkiMessageBoxText[SKI_SHORT_FUNDS_TEXT], amount, MSG_BOX_FLAG_OK, ConfirmDontHaveEnoughForTheDealerMessageBoxCallBack));
3216 			}
3217 			else
3218 			{
3219 				// player doesn't have enough on the table, but can pay for it from his balance
3220 				/// ask player if wants to subtract the shortfall directly from his balance
3221 				DialogueEvent::Add(new DialogueEventSkipAFrame());
3222 				DialogueEvent::Add(new DialogueEventShopkeeperLockTransactionButton(true));
3223 
3224 				ST::string message =
3225 					uiPlayersTotalMoneyValue != 0 ? SkiMessageBoxText[SKI_QUESTION_TO_DEDUCT_MONEY_FROM_PLAYERS_ACCOUNT_TO_COVER_DIFFERENCE] :
3226 					SkiMessageBoxText[SKI_QUESTION_TO_DEDUCT_MONEY_FROM_PLAYERS_ACCOUNT_TO_COVER_COST];
3227 				UINT32 const amount = uiArmsDealersItemsCost - uiPlayersTotalMoneyValue;
3228 				DialogueEvent::Add(new DialogueEventShopkeeperMoney(message, amount, MSG_BOX_FLAG_YESNO, ConfirmToDeductMoneyFromPlayersAccountMessageBoxCallBack));
3229 			}
3230 
3231 			DialogueEvent::Add(new DialogueEventShopkeeperLockTransactionButton(false));
3232 
3233 			gfResetShopKeepIdleQuote = TRUE;
3234 
3235 			// disable further calls to PerformTransaction() until callback calls us again to free this flag
3236 			gfPerformTransactionInProgress = TRUE;
3237 
3238 			return;
3239 		}
3240 
3241 
3242 		// to get this far, player should have the money needed!
3243 		Assert( uiPlayersTotalMoneyValue >= uiArmsDealersItemsCost );
3244 
3245 
3246 		//if the dealer repairs
3247 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
3248 		{
3249 			//Make sure there is enough money in the Player's offer area to cover the repair charge
3250 			if( ( uiMoneyFromPlayersAccount + uiMoneyInPlayersOfferArea ) >= uiArmsDealersItemsCost )
3251 			{
3252 				//Move all the players evaluated items to the dealer ( For a repairman, this means just: get rid of the money )
3253 				MovePlayerOfferedItemsOfValueToArmsDealersInventory();
3254 
3255 				//if the arms dealer is the type of person to give change
3256 				if( SelectedArmsDealer()->hasFlag(ArmsDealerFlag::GIVES_CHANGE) )
3257 				{
3258 					//determine the amount of change to give
3259 					iChangeToGiveToPlayer = ( uiMoneyFromPlayersAccount + uiMoneyInPlayersOfferArea ) - uiArmsDealersItemsCost;
3260 
3261 					//if there is change
3262 					if( iChangeToGiveToPlayer > 0 )
3263 					{
3264 						GivePlayerSomeChange( iChangeToGiveToPlayer );
3265 
3266 						if( uiArmsDealersItemsCost > 0 )
3267 						{
3268 							StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_TOO_MUCH_MONEY_FOR_TRANSACTION );
3269 						}
3270 					}
3271 					else
3272 						StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION );
3273 				}
3274 
3275 
3276 				// move the offered items to the repairman's inventory
3277 				MovePlayersItemsToBeRepairedToArmsDealersInventory();
3278 			}
3279 			else
3280 			{
3281 				//the player doesn't have enough money
3282 				return;
3283 			}
3284 		}
3285 		else // non-repairman
3286 		{
3287 			// DON'T include uiMoneyFromPlayersAccount in change given to player.  The only way this can happen is if he agrees
3288 			// to subtract from his balance, but there isn't enough room.  In that case, the cost of items not transfered is
3289 			// used to reduce his balance deduction instead (not here, in the calling function).
3290 			iChangeToGiveToPlayer = CalculateTotalPlayersValue() - uiArmsDealersItemsCost;
3291 
3292 
3293 			//if the arms dealer buys stuff
3294 			if( ( SelectedArmsDealer()->type == ARMS_DEALER_BUYS_ONLY ) ||
3295 				( SelectedArmsDealer()->type == ARMS_DEALER_BUYS_SELLS ) )
3296 			{
3297 				// but the dealer can't afford this transaction
3298 				if( iChangeToGiveToPlayer > ( INT32 ) gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash )
3299 				{
3300 					// HACK HACK HACK: We forgot to write/record quote 27 for Jake, so he ALWAYS must have enough money!
3301 					if ( gbSelectedArmsDealerID != ARMS_DEALER_JAKE )
3302 					{
3303 						// no deal - dealer can't afford it
3304 						StartShopKeeperTalking( SK_QUOTES_CANT_AFFORD_TO_BUY_OR_TOO_MUCH_TO_REPAIR );
3305 						gfResetShopKeepIdleQuote = TRUE;
3306 
3307 						return;
3308 					}
3309 				}
3310 			}
3311 
3312 
3313 			//Move all the player's evaluated items to the dealer
3314 			MovePlayerOfferedItemsOfValueToArmsDealersInventory();
3315 
3316 			//Move all dealers offered items to the player
3317 			MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea( );
3318 
3319 
3320 			//if the arms dealer is the type of person to give change
3321 			if( SelectedArmsDealer()->hasFlag(ArmsDealerFlag::GIVES_CHANGE) )
3322 			{
3323 				if( iChangeToGiveToPlayer > 0 )
3324 				{
3325 					GivePlayerSomeChange( iChangeToGiveToPlayer );
3326 
3327 					/*
3328 					//Remove the change the arms dealer is returning to the player
3329 					if( ( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash - uiChangeToGiveToPlayer ) >= 0 )
3330 						gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash -= uiChangeToGiveToPlayer;
3331 					else
3332 						gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash = 0;*/
3333 
3334 					if( uiArmsDealersItemsCost == 0 )
3335 					{
3336 						StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION );
3337 					}
3338 					else
3339 					{
3340 						StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_TOO_MUCH_MONEY_FOR_TRANSACTION );
3341 					}
3342 				}
3343 				else if( iChangeToGiveToPlayer == 0 )
3344 					StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION );
3345 			}
3346 
3347 			//if the arms dealer is Howard( 125), set fact 222
3348 			if( gbSelectedArmsDealerID == ARMS_DEALER_HOWARD )
3349 				SetFactTrue(FACT_222);
3350 		}
3351 
3352 
3353 		// add cash transfered from player's account directly to dealer's cash balance
3354 		if( uiMoneyFromPlayersAccount > 0 )
3355 		{
3356 			gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash += uiMoneyFromPlayersAccount;
3357 		}
3358 
3359 		//Redraw the screen
3360 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
3361 
3362 		gfDoneBusinessThisSession = TRUE;
3363 
3364 		//The shopkeeper's inventory has changed, reinitialize
3365 		InitializeShopKeeper( FALSE );
3366 	}
3367 
3368 
3369 	gfResetShopKeepIdleQuote = TRUE;
3370 }
3371 
3372 
3373 static void RemoveItemFromDealersInventory(const INVENTORY_IN_SLOT* pInvSlot, UINT8 ubSlot);
3374 
3375 
MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea(void)3376 static void MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea(void)
3377 {
3378 	//for all items in the dealers items offer area
3379 	UINT32 uiCnt;
3380 	INT8   bSlotID=0;
3381 
3382 	//loop through all the slots in the shopkeeper's offer area
3383 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
3384 	{
3385 		const INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[uiCnt];
3386 		//if there is an item here
3387 		if (a->fActive)
3388 		{
3389 			bSlotID = AddItemToPlayersOfferArea(NO_PROFILE, a, -1);
3390 
3391 			if( bSlotID != -1 )
3392 			{
3393 				PlayersOfferArea[ bSlotID ].uiFlags |= ARMS_INV_JUST_PURCHASED;
3394 
3395 				//if the player has just purchased a VIDEO_CAMERA from Franz Hinkle
3396 				if (gbSelectedArmsDealerID == ARMS_DEALER_FRANZ && a->sItemIndex == VIDEO_CAMERA)
3397 				{
3398 					// set a special flag
3399 					gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__FRANZ_HAS_SOLD_VIDEO_CAMERA_TO_PLAYER;
3400 				}
3401 			}
3402 
3403 			//Remove the items out of the dealers inventory
3404 			RemoveItemFromDealersInventory(a, a->bSlotIdInOtherLocation);
3405 
3406 			//Remove the items from the Shopkeepers Offer area
3407 			if( !RemoveItemFromArmsDealerOfferArea( (UINT8)uiCnt, FALSE ) )//a->bSlotIdInOtherLocation
3408 				SLOGA("MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea: problem removing an item from dealers offer area");
3409 
3410 			Assert(!a->fActive);
3411 		}
3412 	}
3413 
3414 	//loop through all the slots in the shopkeeper's offer area
3415 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
3416 	{
3417 		Assert(!ArmsDealerOfferArea[uiCnt].fActive);
3418 	}
3419 }
3420 
3421 
RemoveItemFromDealersInventory(const INVENTORY_IN_SLOT * pInvSlot,UINT8 ubSlot)3422 static void RemoveItemFromDealersInventory(const INVENTORY_IN_SLOT* pInvSlot, UINT8 ubSlot)
3423 {
3424 	INT16 sInvSlot;
3425 	INT16 sItemID;
3426 	SPECIAL_ITEM_INFO SpclItemInfo;
3427 
3428 	sInvSlot = ubSlot;
3429 	//sInvSlot = ( gSelectArmsDealerInfo.ubCurrentPage - 1 ) * SKI_NUM_ARMS_DEALERS_INV_SLOTS + ubSlot;
3430 
3431 	//Remove all of this item out of the specified inventory slot
3432 	sItemID = gpTempDealersInventory[ sInvSlot ].sItemIndex;
3433 	SetSpecialItemInfoFromObject( &SpclItemInfo, &(pInvSlot->ItemObject) );
3434 	RemoveItemFromArmsDealerInventory( gbSelectedArmsDealerID, sItemID, &SpclItemInfo, pInvSlot->ItemObject.ubNumberOfObjects );
3435 
3436 	gfResetShopKeepIdleQuote = TRUE;
3437 }
3438 
3439 
MovePlayerOfferedItemsOfValueToArmsDealersInventory(void)3440 static void MovePlayerOfferedItemsOfValueToArmsDealersInventory(void)
3441 {
3442 	UINT32	uiCnt;
3443 
3444 	//loop through all the slots in the players offer area
3445 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
3446 	{
3447 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[uiCnt];
3448 		//if there is an item here
3449 		if (o->fActive)
3450 		{
3451 			// and it has value
3452 			if (o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE)
3453 			{
3454 				//Remove the item from the owner merc's inventory
3455 				IfMercOwnedRemoveItemFromMercInv(o);
3456 
3457 				//if the item is money
3458 				if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
3459 				{
3460 					//add the money to the dealers 'cash'
3461 					gArmsDealerStatus[gbSelectedArmsDealerID].uiArmsDealersCash += o->ItemObject.uiMoneyAmount;
3462 				}
3463 				else
3464 				{
3465 					//if the dealer doesn't strictly buy items from the player, give the item to the dealer
3466 					if( SelectedArmsDealer()->type != ARMS_DEALER_BUYS_ONLY )
3467 					{
3468 						// item cease to be merc-owned during this operation
3469 						AddObjectToArmsDealerInventory(gbSelectedArmsDealerID, &o->ItemObject);
3470 					}
3471 				}
3472 
3473 				//erase the item from the player's offer area
3474 				ClearPlayersOfferSlot( uiCnt );
3475 			}
3476 		}
3477 	}
3478 
3479 	gfResetShopKeepIdleQuote = TRUE;
3480 }
3481 
3482 
GetMovingItemOwner()3483 static SOLDIERTYPE* GetMovingItemOwner()
3484 {
3485 	ProfileID const owner_pid = gMoveingItem.ubIdOfMercWhoOwnsTheItem;
3486 #if 0 /* XXX -1 */
3487 	if (owner_pid == -1) return gpSMCurrentMerc;
3488 #else
3489 	if (owner_pid == (UINT8)-1) return gpSMCurrentMerc;
3490 #endif
3491 	SOLDIERTYPE* const owner = FindSoldierByProfileIDOnPlayerTeam(owner_pid);
3492 	return owner ? owner : gpSMCurrentMerc;
3493 }
3494 
3495 
3496 static void DisableAllDealersInventorySlots(void);
3497 static void DisableAllDealersOfferSlots(void);
3498 
3499 
BeginSkiItemPointer(UINT8 ubSource,INT8 bSlotNum,BOOLEAN fOfferToDealerFirst)3500 void BeginSkiItemPointer( UINT8 ubSource, INT8 bSlotNum, BOOLEAN fOfferToDealerFirst )
3501 {
3502 	BOOLEAN fRestrictMouseToRect = FALSE;
3503 	SGPRect Rect;
3504 	OBJECTTYPE TempObject;
3505 
3506 	/*
3507 	// If we are already moving an item
3508 	if ( gMoveingItem.sItemIndex != 0 )
3509 	{
3510 		return;
3511 	}*/
3512 
3513 	switch( ubSource )
3514 	{
3515 		case ARMS_DEALER_INVENTORY:
3516 			//Should never get in here
3517 			SLOGA("BeginSkiItemPointer: invalid Source");
3518 			return;
3519 
3520 		case ARMS_DEALER_OFFER_AREA:
3521 		{
3522 			//Get the item from the slot.
3523 			gMoveingItem = ArmsDealerOfferArea[bSlotNum];
3524 			IfMercOwnedRemoveItemFromMercInv( &gMoveingItem );
3525 
3526 			//remove the item from the slot
3527 			ClearArmsDealerOfferSlot( bSlotNum );
3528 
3529 
3530 			//Restrict the cursor to the arms dealer offer area, players offer area and the players inventory
3531 			Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X;
3532 			Rect.iTop = SKI_DEALER_OFFER_AREA_Y;
3533 			Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH;
3534 			Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT;
3535 			fRestrictMouseToRect = TRUE;
3536 
3537 			SOLDIERTYPE* const owner = GetMovingItemOwner();
3538 			SetItemPointer(&gMoveingItem.ItemObject, owner);
3539 			break;
3540 		}
3541 
3542 		case PLAYERS_OFFER_AREA:
3543 		{
3544 			INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bSlotNum];
3545 			//Get the item from the slot.
3546 			gMoveingItem = *o;
3547 
3548 			// if the slot is overloaded (holds more objects than we have room for valid statuses of)
3549 			if (o->ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT )
3550 			{
3551 				// allow only MAX_OBJECTS_PER_SLOT of those objects to be picked up at a time
3552 				// (sure it kind of sucks, but it's a lot easier than handling overloaded cursor objects in Interface Items!
3553 				gMoveingItem.ItemObject.ubNumberOfObjects = MAX_OBJECTS_PER_SLOT;
3554 
3555 				// decrease the number objects left in the slot by that much instead of deleting it
3556 				o->ItemObject.ubNumberOfObjects -= MAX_OBJECTS_PER_SLOT;
3557 			}
3558 			else	// completely legal object
3559 			{
3560 				// NOTE: a merc-owned object can never be "overloaded", so no problem here
3561 				IfMercOwnedRemoveItemFromMercInv( &gMoveingItem );
3562 
3563 				//remove the item from the slot
3564 				ClearPlayersOfferSlot( bSlotNum );
3565 			}
3566 
3567 			//Restrict the cursor to the players offer area and the players inventory
3568 			Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X;
3569 			Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y;
3570 			Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH;
3571 			Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT;
3572 			fRestrictMouseToRect = TRUE;
3573 
3574 			SOLDIERTYPE* const owner = GetMovingItemOwner();
3575 			SetItemPointer(&gMoveingItem.ItemObject, owner);
3576 			break;
3577 		}
3578 
3579 		case PLAYERS_INVENTORY:
3580 			// better be a valid merc pocket index, or -1
3581 			Assert( ( bSlotNum >= -1 ) && ( bSlotNum < NUM_INV_SLOTS ) );
3582 
3583 			// if we're supposed to store the original pocket #, but that pocket still holds more of these
3584 			if ( ( bSlotNum != -1 ) && ( gpSMCurrentMerc->inv[ bSlotNum ].ubNumberOfObjects > 0 ) )
3585 			{
3586 				// then we can't store the pocket #, because our system can't return stacked objects
3587 				bSlotNum = -1;
3588 			}
3589 
3590 			// First try to evaluate the item immediately like any other left-click on a merc's inventory slot would be.  Only
3591 			// if that doesn't work (because there isn't enough room in the player's offer area), the item is picked up into
3592 			// the cursor, and may then get placed into the player's offer area directly, but it will NOT get evaluated that
3593 			// way, and so has no possibility of entering the dealer's inventory (where complex items aren't permitted).
3594 			if ( fOfferToDealerFirst && OfferObjectToDealer( gpItemPointer, gpSMCurrentMerc->ubProfile, bSlotNum ) )
3595 			{
3596 				//Reset the cursor
3597 				SetSkiCursor( CURSOR_NORMAL );
3598 			}
3599 			else	// not supposed to offer it, or dealer has no more room
3600 			{
3601 				// store the current contents of the cursor in a temporary object structure.
3602 				// We have to do this before memsetting gMoveingItem, 'cause during swaps, gpItemPointer == &gMoveingItem.ItemObject!
3603 				TempObject = *gpItemPointer;
3604 
3605 				//ARM: The memset was previously commented out, in order to preserve the owning merc's inv slot # during a swap of
3606 				// items in an inventory slot.  However, that leads to other bugs: if you picked the thing you're swapping in from
3607 				// a restricted inv slot (headgear, vest, etc.), the item swapped out will end up belonging to an illegal slot, and
3608 				// return there with a right click on it in the player's offer area.  So now ALL items picked up here are unowned.
3609 				gMoveingItem = INVENTORY_IN_SLOT{};
3610 
3611 				//Get the item from the pointer
3612 				gMoveingItem.ItemObject = TempObject;
3613 
3614 				gMoveingItem.fActive = TRUE;
3615 				gMoveingItem.sItemIndex = TempObject.usItem;
3616 				gMoveingItem.ubLocationOfObject = ubSource;
3617 
3618 				// By necessity, these items don't belong to a slot (so you can't return them via a right click),
3619 				// because it would be too much work to handle attachments, members of a stack, or even items swapped out of slots.
3620 				gMoveingItem.ubIdOfMercWhoOwnsTheItem = gpSMCurrentMerc->ubProfile;
3621 				gMoveingItem.bSlotIdInOtherLocation = bSlotNum;
3622 
3623 				//Restrict the cursor to the players offer area and the players inventory
3624 				Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X;
3625 				Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y;
3626 				Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH;
3627 				Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT;
3628 				fRestrictMouseToRect = TRUE;
3629 
3630 				SetItemPointer(&gMoveingItem.ItemObject, gpSMCurrentMerc);
3631 			}
3632 
3633 			break;
3634 	}
3635 
3636 	// if we have something in hand
3637 	if ( gpItemPointer != NULL )
3638 	{
3639 		//make sure the soldier is not null
3640 		Assert( gpItemPointerSoldier != NULL );
3641 
3642 		// Set mouse
3643 		SetSkiCursor( EXTERN_CURSOR );
3644 
3645 		//Enable the region that limits the movement of the cursor with the item
3646 		gSkiInventoryMovementAreaMouseRegions.Enable();
3647 
3648 		if ( fRestrictMouseToRect )
3649 		{
3650 			RestrictMouseCursor( &Rect );
3651 		}
3652 
3653 		DisableAllDealersInventorySlots();
3654 
3655 		if ( ubSource != ARMS_DEALER_OFFER_AREA )
3656 		{
3657 			DisableAllDealersOfferSlots();
3658 		}
3659 
3660 		gfResetShopKeepIdleQuote = TRUE;
3661 	}
3662 
3663 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
3664 }
3665 
3666 
RestrictSkiMouseCursor()3667 void RestrictSkiMouseCursor()
3668 {
3669 	SGPRect Rect;
3670 
3671 	Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X;
3672 	Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y;
3673 	Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH;
3674 	Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT;
3675 
3676 	RestrictMouseCursor( &Rect );
3677 }
3678 
3679 
3680 static void EnableAllDealersInventorySlots(void);
3681 static void EnableAllDealersOfferSlots(void);
3682 
3683 
SetSkiCursor(UINT16 usCursor)3684 void SetSkiCursor( UINT16 usCursor )
3685 {
3686 	UINT8 ubCnt;
3687 
3688 	//if we are setting up an item as a cursor
3689 	if( usCursor == EXTERN_CURSOR )
3690 	{
3691 		//EnableSMPanelButtons( FALSE, FALSE );
3692 
3693 		// if the current merc is in range
3694 		if( !gfSMDisableForItems )
3695 		{
3696 			// hatch out unavailable merc inventory slots
3697 			ReevaluateItemHatches( gpSMCurrentMerc, FALSE );
3698 		}
3699 
3700 		SetMouseCursorFromItem(gMoveingItem.sItemIndex);
3701 
3702 		gSMPanelRegion.ChangeCursor(usCursor);
3703 		gSKI_EntireScreenMouseRegions.ChangeCursor(usCursor);
3704 		gArmsDealersDropItemToGroundMouseRegions.ChangeCursor(usCursor);
3705 		MSYS_SetCurrentCursor( usCursor );
3706 
3707 		//if the item desc window is up
3708 		if( gInvDesc.uiFlags & MSYS_REGION_EXISTS )
3709 			gInvDesc.ChangeCursor(usCursor);
3710 
3711 		for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++)
3712 		{
3713 			if( gItemDescAttachmentRegions[ubCnt].uiFlags & MSYS_REGION_EXISTS )
3714 				gItemDescAttachmentRegions[ubCnt].ChangeCursor(usCursor);
3715 		}
3716 
3717 		for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
3718 		{
3719 			gPlayersOfferSlotsMouseRegions[ubCnt].ChangeCursor(usCursor);
3720 			gPlayersOfferSlotsSmallFaceMouseRegions[ubCnt].ChangeCursor(usCursor);
3721 			gDealersOfferSlotsMouseRegions[ubCnt].ChangeCursor(usCursor);
3722 
3723 			//if the dealer repairs
3724 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
3725 			{
3726 				gDealersOfferSlotsSmallFaceMouseRegions[ubCnt].ChangeCursor(usCursor);
3727 			}
3728 		}
3729 
3730 		gSkiInventoryMovementAreaMouseRegions.ChangeCursor(usCursor);
3731 	}
3732 
3733 	//else we are restoring the old cursor
3734 	else
3735 	{
3736 		gMoveingItem = INVENTORY_IN_SLOT{};
3737 
3738 		gpItemPointer = NULL;
3739 
3740 		DisableTacticalTeamPanelButtons( FALSE );
3741 
3742 		//EnableSMPanelButtons( TRUE, FALSE );
3743 		//CheckForDisabledForGiveItem( );
3744 
3745 		// if the current merc is in range
3746 		if( !gfSMDisableForItems )
3747 		{
3748 			// make all merc inventory slots available again
3749 			ReevaluateItemHatches( gpSMCurrentMerc, TRUE );
3750 		}
3751 
3752 		gSMPanelRegion.ChangeCursor(usCursor);
3753 		gSKI_EntireScreenMouseRegions.ChangeCursor(usCursor);
3754 		gArmsDealersDropItemToGroundMouseRegions.ChangeCursor(usCursor);
3755 
3756 		for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
3757 		{
3758 			gPlayersOfferSlotsMouseRegions[ubCnt].ChangeCursor(usCursor);
3759 			gPlayersOfferSlotsSmallFaceMouseRegions[ubCnt].ChangeCursor(usCursor);
3760 			gDealersOfferSlotsMouseRegions[ubCnt].ChangeCursor(usCursor);
3761 
3762 			//if the dealer repairs
3763 			if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
3764 			{
3765 				gDealersOfferSlotsSmallFaceMouseRegions[ubCnt].ChangeCursor(usCursor);
3766 			}
3767 		}
3768 
3769 		//if the item desc window is up
3770 		if( gInvDesc.uiFlags & MSYS_REGION_EXISTS )
3771 			gInvDesc.ChangeCursor(usCursor);
3772 
3773 		for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++)
3774 		{
3775 			if( gItemDescAttachmentRegions[ubCnt].uiFlags & MSYS_REGION_EXISTS )
3776 				gItemDescAttachmentRegions[ubCnt].ChangeCursor(usCursor);
3777 		}
3778 
3779 		gSkiInventoryMovementAreaMouseRegions.ChangeCursor(usCursor);
3780 
3781 		MSYS_SetCurrentCursor( usCursor );
3782 
3783 		SetCurrentCursorFromDatabase( usCursor );
3784 
3785 		FreeMouseCursor();
3786 
3787 		EnableAllDealersInventorySlots();
3788 		EnableAllDealersOfferSlots();
3789 
3790 		//Disable the region that limits the movement of the cursor with the item
3791 		gSkiInventoryMovementAreaMouseRegions.Disable();
3792 	}
3793 
3794 	SetCurrentCursorFromDatabase( usCursor );
3795 
3796 	// make sure disabled slot hatching gets updated when items picked up / dropped
3797 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
3798 }
3799 
3800 
AddInventoryToSkiLocation(const INVENTORY_IN_SLOT * pInv,UINT8 ubSpotLocation,UINT8 ubWhere)3801 static INT8 AddInventoryToSkiLocation(const INVENTORY_IN_SLOT* pInv, UINT8 ubSpotLocation, UINT8 ubWhere)
3802 {
3803 	INT8 bSlotAddedTo = -1;
3804 
3805 
3806 	switch( ubWhere )
3807 	{
3808 		case ARMS_DEALER_INVENTORY:
3809 		case PLAYERS_INVENTORY:
3810 			// not used this way
3811 			SLOGA("AddInventoryToSkiLocation: invalid Where");
3812 			return( bSlotAddedTo );
3813 
3814 		case ARMS_DEALER_OFFER_AREA:
3815 		{
3816 			INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[ubSpotLocation];
3817 			//If we can add the item into the slot that was clicked on
3818 			if (!a->fActive)
3819 			{
3820 				*a = *pInv;
3821 				IfMercOwnedCopyItemToMercInv( pInv );
3822 
3823 				SetSkiRegionHelpText(    a, &gDealersOfferSlotsMouseRegions[ubSpotLocation],          ARMS_DEALER_OFFER_AREA);
3824 				SetSkiFaceRegionHelpText(a, &gDealersOfferSlotsSmallFaceMouseRegions[ubSpotLocation], ARMS_DEALER_OFFER_AREA);
3825 
3826 				bSlotAddedTo = ubSpotLocation;
3827 			}
3828 			else
3829 			{
3830 				bSlotAddedTo = AddItemToArmsDealerOfferArea( pInv, -1 );
3831 			}
3832 			break;
3833 		}
3834 
3835 		case PLAYERS_OFFER_AREA:
3836 		{
3837 			//If we can add the item into the slot that was clicked on
3838 			INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubSpotLocation];
3839 			if (!o->fActive)
3840 			{
3841 				// put it down in that player offer area slot
3842 				*o = *pInv;
3843 				IfMercOwnedCopyItemToMercInv( pInv );
3844 
3845 				//if the item is money
3846 				if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
3847 				{
3848 					//Since money is always evaluated
3849 					o->uiFlags     |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE;
3850 					o->uiItemPrice  = o->ItemObject.uiMoneyAmount;
3851 				}
3852 
3853 				SetSkiRegionHelpText(    o, &gPlayersOfferSlotsMouseRegions[ubSpotLocation],          PLAYERS_OFFER_AREA);
3854 				SetSkiFaceRegionHelpText(o, &gPlayersOfferSlotsSmallFaceMouseRegions[ubSpotLocation], PLAYERS_OFFER_AREA);
3855 
3856 				bSlotAddedTo = ubSpotLocation;
3857 			}
3858 			//else if( )
3859 			//{
3860 				//check to see the type we are adding is the same as the type that is already there
3861 			//}
3862 			else	// that slot is full
3863 			{
3864 				// add it elsewhere
3865 				bSlotAddedTo = AddItemToPlayersOfferArea( pInv->ubIdOfMercWhoOwnsTheItem, pInv, pInv->bSlotIdInOtherLocation );
3866 			}
3867 			break;
3868 		}
3869 	}
3870 
3871 	//Redraw the screen
3872 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
3873 	gfResetShopKeepIdleQuote = TRUE;
3874 	fInterfacePanelDirty = DIRTYLEVEL2;
3875 
3876 	return( bSlotAddedTo );
3877 }
3878 
3879 
DisplayTalkingArmsDealer(void)3880 static void DisplayTalkingArmsDealer(void)
3881 {
3882 	static BOOLEAN fWasTheMercTalking= FALSE;
3883 	//static UINT32 uiLastTime=0;
3884 	//UINT32 uiCurTime = GetJA2Clock();
3885 	//static UINT32 uiMinimumLengthForTalkingText;
3886 
3887 
3888 	//Make sure the Dealers doesn't get disabled
3889 	giShopKeeperFaceIndex->fDisabled = FALSE;
3890 
3891 	HandleDialogue();
3892 
3893 	//Gets handled when we render the tactical interface
3894 	HandleAutoFaces( );
3895 
3896 	HandleTalkingAutoFaces( );
3897 	HandleShopKeeperDialog( 0 );
3898 
3899 	gfIsTheShopKeeperTalking = giShopKeeperFaceIndex->fTalking;
3900 
3901 	//if the merc just started talking
3902 	//if( gfIsTheShopKeeperTalking && !fWasTheMercTalking )
3903 	{
3904 
3905 	}
3906 
3907 	//if the merc is talking
3908 	if( gfIsTheShopKeeperTalking )
3909 		fWasTheMercTalking = TRUE;
3910 
3911 	//if the merc just finished talking
3912 	if( !gfIsTheShopKeeperTalking && fWasTheMercTalking )
3913 	{
3914 		RemoveShopKeeperSubTitledText();
3915 		fWasTheMercTalking = FALSE;
3916 	}
3917 }
3918 
3919 
3920 static void DealWithItemsStillOnTheTable(void);
3921 
3922 
HandleShopKeeperDialog(UINT8 ubInit)3923 static void HandleShopKeeperDialog(UINT8 ubInit)
3924 {
3925 	UINT32 uiCurTime = GetJA2Clock();
3926 
3927 	static UINT32 uiLastTime = 0;
3928 	static INT8   bSpeech = -1;
3929 
3930 
3931 	if( ubInit >= 1 )
3932 	{
3933 		uiLastTime = GetJA2Clock();
3934 	}
3935 
3936 	if( ubInit == 1 )
3937 	{
3938 		// special: if it's Arnie, and we have stuff in for repairs, but it's not fixed yet, use a different opening quote!
3939 		if ( ( gbSelectedArmsDealerID == ARMS_DEALER_ARNIE ) && RepairmanIsFixingItemsButNoneAreDoneYet( ARNIE ) )
3940 		{
3941 			bSpeech = ARNIE_QUOTE_NOT_REPAIRED_YET;
3942 		}
3943 		// if repairs were delayed
3944 		if ( gfStartWithRepairsDelayedQuote )
3945 		{
3946 			bSpeech = FREDO_PERKO_SORRY_REPAIR_DELAYED;
3947 			gfStartWithRepairsDelayedQuote = FALSE;
3948 
3949 			// treat this as having done business - the player WAS here for a good reason, and it's the dealers fault...
3950 			gfDoneBusinessThisSession = TRUE;
3951 		}
3952 		else
3953 		{
3954 			bSpeech = SK_QUOTES_PLAYER_FIRST_ENTERS_SKI;
3955 		}
3956 		return;
3957 	}
3958 
3959 
3960 	//if its the first time in
3961 	if( bSpeech != -1 )
3962 	{
3963 		if( ( uiCurTime - uiLastTime ) > 800 )
3964 		{
3965 			StartShopKeeperTalking( bSpeech );
3966 			bSpeech = -1;
3967 			uiLastTime = GetJA2Clock();
3968 		}
3969 	}
3970 
3971 
3972 	//Handle Idle converstions
3973 	if( gfIsTheShopKeeperTalking )
3974 	{
3975 		uiLastTime = uiCurTime;
3976 	}
3977 	//if the player has requested to leave
3978 	else if( gfUserHasRequestedToLeave && ( giShopKeepDialogueEventinProgress == -1 ) )
3979 	{
3980 		// to see if the player has finished talking
3981 		if(!giShopKeeperFaceIndex->fTalking)
3982 		{
3983 			class DialogueEventShopkeeperExit : public DialogueEvent
3984 			{
3985 				public:
3986 					bool Execute()
3987 					{
3988 						gfSKIScreenExit = TRUE;
3989 						return false;
3990 					}
3991 			};
3992 
3993 			DialogueEvent::Add(new DialogueEventShopkeeperExit());
3994 		}
3995 
3996 		DealWithItemsStillOnTheTable();
3997 	}
3998 	else
3999 	{
4000 		// Determine if the shopkeeper should say a quote
4001 		if( ( uiCurTime - uiLastTime ) > ( guiRandomQuoteDelayTime + Random( guiRandomQuoteDelayTime ) ) )
4002 		{
4003 			//Only say it occasionally
4004 			if( Chance ( 35 ) )
4005 			{
4006 				INT16 sRandomQuoteToUse = -1;
4007 
4008 				// first check if one of the situation warrants one of the more precise quotes
4009 
4010 				//if the dealer repairs
4011 				if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
4012 				{
4013 					// if there are items in the arms dealer's offer area (must be awaiting repairs)
4014 					if ( CountNumberOfItemsInTheArmsDealersOfferArea() > 0)
4015 					{
4016 						sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_SELL_OR_REPAIR;
4017 					}
4018 				}
4019 				else	// non-repairman
4020 				{
4021 					// if there is any items in the player's area with value to this dealer other than just money
4022 					if ( ( CalculateTotalPlayersValue() - CalculateHowMuchMoneyIsInPlayersOfferArea( ) ) > 0 )
4023 					{
4024 						// if the player has also selected some items for purchase
4025 						if ( CountNumberOfItemsInTheArmsDealersOfferArea() > 0 )
4026 						{
4027 							sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_TRADE;
4028 						}
4029 						else
4030 						{
4031 							sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_SELL_OR_REPAIR;
4032 						}
4033 					}
4034 				}
4035 
4036 				// if neither of the more precise quotes fit, or 33 percent of the time anyways
4037 				if ( ( sRandomQuoteToUse == -1 ) || Chance( 33 ) )
4038 				{
4039 					if( Chance ( 50 ) )
4040 						sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_1;
4041 					else
4042 						sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_2;
4043 				}
4044 
4045 				Assert( sRandomQuoteToUse != -1 );
4046 				Assert( sRandomQuoteToUse < NUM_COMMON_SK_QUOTES );
4047 
4048 				if ( !gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] )
4049 				{
4050 					StartShopKeeperTalking( (UINT16) sRandomQuoteToUse );
4051 
4052 					gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] = TRUE;
4053 
4054 					//increase the random quote delay
4055 					guiRandomQuoteDelayTime += SKI_DEALERS_RANDOM_QUOTE_DELAY_INCREASE_RATE;
4056 				}
4057 			}
4058 
4059 			uiLastTime = GetJA2Clock();
4060 		}
4061 	}
4062 }
4063 
4064 
StartShopKeeperTalking(UINT16 usQuoteNum)4065 static BOOLEAN StartShopKeeperTalking(UINT16 usQuoteNum)
4066 {
4067 	// if already in the process of leaving, don't start any additional quotes
4068 	if ( gfSKIScreenExit || gfRemindedPlayerToPickUpHisStuff || gfUserHasRequestedToLeave )
4069 	{
4070 		return( FALSE );
4071 	}
4072 
4073 	class DialogueEventShopkeeperSetQuote : public DialogueEvent
4074 	{
4075 		public:
4076 			DialogueEventShopkeeperSetQuote(INT32 const quote) : quote_(quote) {}
4077 
4078 			bool Execute()
4079 			{
4080 				giShopKeepDialogueEventinProgress = quote_;
4081 				return false;
4082 			}
4083 
4084 		private:
4085 			INT32 const quote_;
4086 	};
4087 
4088 	// post event to mark shopkeeper dialogue in progress
4089 	DialogueEvent::Add(new DialogueEventShopkeeperSetQuote(usQuoteNum));
4090 
4091 	// post quote dialogue
4092 	CharacterDialogue(SelectedArmsDealer()->profileID, usQuoteNum, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI, FALSE);
4093 
4094 	// post event to mark shopkeeper dialogue as ended
4095 	DialogueEvent::Add(new DialogueEventShopkeeperSetQuote(-1));
4096 
4097 	gfResetShopKeepIdleQuote = TRUE;
4098 	return( TRUE );
4099 }
4100 
4101 
IsGunOrAmmoOfSameTypeSelected(OBJECTTYPE const & o)4102 static bool IsGunOrAmmoOfSameTypeSelected(OBJECTTYPE const& o)
4103 {
4104 	if (!gpHighLightedItemObject) return false; // No item selected
4105 	OBJECTTYPE const& highlighted_o    = *gpHighLightedItemObject;
4106 	const ItemModel * highlighted_item = GCM->getItem(highlighted_o.usItem);
4107 	const ItemModel * o_item = GCM->getItem(o.usItem);
4108 
4109 	// Is one ammo for the other?
4110 	if (highlighted_item->getItemClass() == IC_AMMO)
4111 	{
4112 		if (o_item->getItemClass() == IC_GUN &&
4113 				GCM->getWeapon(o.usItem)->matches(highlighted_item->asAmmo()->calibre))
4114 		{
4115 			return true;
4116 		}
4117 	}
4118 	else if (highlighted_item->getItemClass() == IC_GUN)
4119 	{
4120 		if (o_item->getItemClass() == IC_AMMO &&
4121 				GCM->getWeapon(highlighted_o.usItem)->matches(o_item->asAmmo()->calibre))
4122 		{
4123 			return true;
4124 		}
4125 	}
4126 
4127 	// Is one an attachment for the other?
4128 	if (o_item->getFlags() & ITEM_ATTACHMENT)
4129 	{
4130 		if (ValidAttachment(o.usItem, highlighted_o.usItem)) return true;
4131 	}
4132 	else
4133 	{
4134 		if (ValidAttachment(highlighted_o.usItem, o.usItem)) return true;
4135 	}
4136 
4137 	return false;
4138 }
4139 
4140 
4141 static void ShopKeeperSubTitleRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason);
4142 
4143 
InitShopKeeperSubTitledText(const ST::string & str)4144 void InitShopKeeperSubTitledText(const ST::string& str)
4145 {
4146 	//Now setup the popup box
4147 	if( gGameSettings.fOptions[ TOPTION_SUBTITLES ] )
4148 	{
4149 		UINT16 usActualWidth=0;
4150 		UINT16 usActualHeight=0;
4151 
4152 		// The subutitled text for what the merc is saying
4153 		ST::string ShopKeeperTalkingText = ST::format("\"{}\"", str);
4154 		g_popup_box = PrepareMercPopupBox(g_popup_box, BASIC_MERC_POPUP_BACKGROUND, BASIC_MERC_POPUP_BORDER, ShopKeeperTalkingText, 300, 0, 0, 0, &usActualWidth, &usActualHeight);
4155 
4156 		//position it to start under the guys face
4157 		gusPositionOfSubTitlesX = 13;
4158 
4159 		RenderMercPopUpBox(g_popup_box, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER);
4160 
4161 		//check to make sure the region is not already initialized
4162 		if( !( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS ) )
4163 		{
4164 			MSYS_DefineRegion(&gShopKeeperSubTitleMouseRegion, gusPositionOfSubTitlesX,
4165 						SKI_POSITION_SUBTITLES_Y,
4166 						(INT16)(gusPositionOfSubTitlesX + usActualWidth),
4167 						(INT16)(SKI_POSITION_SUBTITLES_Y + usActualHeight),
4168 						MSYS_PRIORITY_HIGH,
4169 						CURSOR_NORMAL, MSYS_NO_CALLBACK,
4170 						ShopKeeperSubTitleRegionCallBack);
4171 		}
4172 
4173 		//redraw the screen
4174 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
4175 
4176 	}
4177 }
4178 
4179 
RemoveShopKeeperSubTitledText(void)4180 static void RemoveShopKeeperSubTitledText(void)
4181 {
4182 	if (!g_popup_box) return;
4183 
4184 	RemoveMercPopupBox(g_popup_box);
4185 	g_popup_box = 0;
4186 
4187 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
4188 
4189 	//Get rid of the subtitles region
4190 	if( gGameSettings.fOptions[ TOPTION_SUBTITLES ] )
4191 		MSYS_RemoveRegion( &gShopKeeperSubTitleMouseRegion );
4192 }
4193 
4194 
AreThereItemsInTheArmsDealersOfferArea()4195 static bool AreThereItemsInTheArmsDealersOfferArea()
4196 {
4197 	FOR_EACH(INVENTORY_IN_SLOT const, i, ArmsDealerOfferArea)
4198 	{
4199 		if (i->fActive) return true;
4200 	}
4201 	return false;
4202 }
4203 
4204 
AreThereItemsInThePlayersOfferArea()4205 static bool AreThereItemsInThePlayersOfferArea()
4206 {
4207 	FOR_EACH(INVENTORY_IN_SLOT const, i, PlayersOfferArea)
4208 	{
4209 		if (i->fActive) return true;
4210 	}
4211 	return false;
4212 }
4213 
4214 
4215 //Mouse Call back for the Arms traders inventory slot
ShopKeeperSubTitleRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)4216 static void ShopKeeperSubTitleRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4217 {
4218 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP || iReason & MSYS_CALLBACK_REASON_RBUTTON_UP)
4219 	{
4220 		ShutUpShopKeeper();
4221 	}
4222 }
4223 
4224 
4225 //Mouse Call back for the Arms delaers face
SelectArmsDealersFaceRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)4226 static void SelectArmsDealersFaceRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
4227 {
4228 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP || iReason & MSYS_CALLBACK_REASON_RBUTTON_UP)
4229 	{
4230 		ShutUpShopKeeper();
4231 	}
4232 }
4233 
4234 
ShutUpShopKeeper(void)4235 static void ShutUpShopKeeper(void)
4236 {
4237 	//RemoveShopKeeperSubTitledText();
4238 
4239 	ShutupaYoFace( giShopKeeperFaceIndex );
4240 	gfIsTheShopKeeperTalking = FALSE;
4241 
4242 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
4243 }
4244 
4245 
CountNumberOfValuelessItemsInThePlayersOfferArea()4246 static UINT8 CountNumberOfValuelessItemsInThePlayersOfferArea()
4247 {
4248 	UINT8 n = 0;
4249 	FOR_EACH(INVENTORY_IN_SLOT const, i, PlayersOfferArea)
4250 	{
4251 		INVENTORY_IN_SLOT const& o = *i;
4252 		if (!o.fActive)                                  continue;
4253 		if (o.uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE) continue;
4254 		++n;
4255 	}
4256 	return n;
4257 }
4258 
4259 
CountNumberOfItemsInThePlayersOfferArea()4260 static UINT8 CountNumberOfItemsInThePlayersOfferArea()
4261 {
4262 	UINT8 n = 0;
4263 	FOR_EACH(INVENTORY_IN_SLOT const, i, PlayersOfferArea)
4264 	{
4265 		if (i->fActive) ++n;
4266 	}
4267 	return n;
4268 }
4269 
4270 
CountNumberOfItemsInTheArmsDealersOfferArea()4271 static UINT8 CountNumberOfItemsInTheArmsDealersOfferArea()
4272 {
4273 	UINT8 n = 0;
4274 	FOR_EACH(INVENTORY_IN_SLOT const, i, ArmsDealerOfferArea)
4275 	{
4276 		if (i->fActive) ++n;
4277 	}
4278 	return n;
4279 }
4280 
4281 
GetSlotNumberForMerc(UINT8 ubProfile)4282 static INT8 GetSlotNumberForMerc(UINT8 ubProfile)
4283 {
4284 	INT8 bCnt;
4285 
4286 	for( bCnt = 0; bCnt < gubNumberMercsInArray; bCnt++ )
4287 	{
4288 		if( gubArrayOfEmployedMercs[ bCnt ] == ubProfile )
4289 			return( bCnt );
4290 	}
4291 
4292 	// not found - not currently working for the player
4293 	return( -1 );
4294 }
4295 
4296 
EnableDisableDealersInventoryPageButtons(void)4297 static void EnableDisableDealersInventoryPageButtons(void)
4298 {
4299 	//if we are on the first page, disable the page up arrow
4300 	EnableButton(guiSKI_InvPageUpButton, gSelectArmsDealerInfo.ubCurrentPage > 1);
4301 
4302 	//if we are on the last page, disable the page down button
4303 	EnableButton(guiSKI_InvPageDownButton, gSelectArmsDealerInfo.ubCurrentPage != gSelectArmsDealerInfo.ubNumberOfPages);
4304 }
4305 
4306 
EnableDisableEvaluateAndTransactionButtons(void)4307 static void EnableDisableEvaluateAndTransactionButtons(void)
4308 {
4309 	UINT8   ubCnt;
4310 	BOOLEAN fItemEvaluated=FALSE;
4311 	UINT32  uiArmsDealerTotalCost = CalculateTotalArmsDealerCost();
4312 	UINT32  uiPlayersOfferAreaTotalCost = CalculateTotalPlayersValue();
4313 	UINT32  uiPlayersOfferAreaTotalMoney = CalculateHowMuchMoneyIsInPlayersOfferArea( );
4314 
4315 
4316 	//loop through the players offer area
4317 	//loop through all the items in the players offer area and determine if they can be sold here.
4318 	for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
4319 	{
4320 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubCnt];
4321 		//if there is an item here
4322 		if (o->fActive)
4323 		{
4324 			//if the item has value
4325 			if (o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE)
4326 			{
4327 				//if the item isnt money ( which is always evaluated )
4328 				if (GCM->getItem(o->sItemIndex)->getItemClass() != IC_MONEY)
4329 				{
4330 					fItemEvaluated = TRUE;
4331 				}
4332 				else if (!DoesDealerDoRepairs(gbSelectedArmsDealerID) && GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
4333 				{
4334 					//else if it is not a repair dealer, and the item is money
4335 					fItemEvaluated = TRUE;
4336 				}
4337 			}
4338 		}
4339 
4340 		//if the dealer is a repair dealer
4341 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
4342 		{
4343 			//if there is an item here, the item has to have been evaluated
4344 			if( ArmsDealerOfferArea[ ubCnt ].fActive )
4345 			{
4346 				fItemEvaluated = TRUE;
4347 			}
4348 		}
4349 	}
4350 
4351 	//if there are evaluated items here
4352 	if( fItemEvaluated )
4353 	{
4354 		////and if the players offer area value exceeds the shopkeeper's area
4355 		//if( uiPlayersOfferAreaTotalCost >= uiArmsDealerTotalCost )
4356 			EnableButton( guiSKI_TransactionButton );
4357 		//else
4358 		//	DisableButton( guiSKI_TransactionButton );
4359 
4360 		/*
4361 		//if the only item in the players offer area is money, and there is nothing in the dealers offer area
4362 		if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && uiArmsDealerTotalCost == 0 )
4363 			DisableButton( guiSKI_TransactionButton );*/
4364 
4365 		//If its a repair dealer, and there is no items in the Dealer Offer area,
4366 		if(DoesDealerDoRepairs(gbSelectedArmsDealerID)&&
4367 				CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 &&
4368 				uiPlayersOfferAreaTotalMoney < uiArmsDealerTotalCost
4369 			)
4370 			DisableButton( guiSKI_TransactionButton );
4371 	}
4372 	//else if there is
4373 	else
4374 	{
4375 		EnableButton(guiSKI_TransactionButton, uiArmsDealerTotalCost != 0);
4376 	}
4377 
4378 
4379 	if( uiArmsDealerTotalCost > uiPlayersOfferAreaTotalCost + LaptopSaveInfo.iCurrentBalance )
4380 	{
4381 		DisableButton( guiSKI_TransactionButton );
4382 	}
4383 	/* Allow transaction attempt when dealer can't afford to buy that much - he'll reject it with a special quote!
4384 	else if( ( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash + uiArmsDealerTotalCost ) < ( uiPlayersOfferAreaTotalCost - uiPlayersOfferAreaTotalMoney ) )
4385 	{
4386 		DisableButton( guiSKI_TransactionButton );
4387 	}*/
4388 	else if( ( uiPlayersOfferAreaTotalCost == uiPlayersOfferAreaTotalMoney ) && ( uiPlayersOfferAreaTotalMoney > 0 ) && ( uiArmsDealerTotalCost == 0 ) )
4389 	{
4390 		DisableButton( guiSKI_TransactionButton );
4391 	}
4392 
4393 
4394 	//if the player is currently moving an item, disable the transaction button
4395 	if( gMoveingItem.sItemIndex != 0 )
4396 	{
4397 		DisableButton( guiSKI_TransactionButton );
4398 		DisableButton( guiSKI_DoneButton );
4399 	}
4400 	else
4401 	{
4402 		EnableButton( guiSKI_DoneButton );
4403 	}
4404 
4405 
4406 	//ARM: Always permit trying bribes, even if they don't work on a given dealer!
4407 	// if the arms dealer is the kind of person who accepts gifts, and there is stuff to take
4408 	//if( SelectedArmsDealer()->hasFlag(ArmsDealerFlag::ACCEPTS_GIFTS )
4409 	{
4410 		//if the player is giving the dealer money, without buying anything
4411 		if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 )
4412 		{
4413 			EnableButton( guiSKI_TransactionButton );
4414 		}
4415 	}
4416 
4417 	if( gfUserHasRequestedToLeave )
4418 		DisableButton( guiSKI_TransactionButton );
4419 
4420 
4421 	//if the player is in the item desc for the arms dealer items
4422 	if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL )
4423 	{
4424 		guiSKI_TransactionButton->uiFlags |= BUTTON_FORCE_UNDIRTY;
4425 		guiSKI_DoneButton->uiFlags        |= BUTTON_FORCE_UNDIRTY;
4426 	}
4427 	else
4428 	{
4429 		guiSKI_TransactionButton->uiFlags &= ~BUTTON_FORCE_UNDIRTY;
4430 		guiSKI_DoneButton->uiFlags        &= ~BUTTON_FORCE_UNDIRTY;
4431 	}
4432 }
4433 
4434 
AddItemToPlayersOfferAreaAfterShopKeeperOpen(OBJECTTYPE * pItemObject,INT8 bPreviousInvPos)4435 void AddItemToPlayersOfferAreaAfterShopKeeperOpen( OBJECTTYPE *pItemObject, INT8 bPreviousInvPos )
4436 {
4437 	gItemToAdd.fActive = TRUE;
4438 	gItemToAdd.ItemObject = *pItemObject;
4439 	gItemToAdd.bPreviousInvPos	= bPreviousInvPos;
4440 }
4441 
4442 
IsMoneyTheOnlyItemInThePlayersOfferArea(void)4443 static BOOLEAN IsMoneyTheOnlyItemInThePlayersOfferArea(void)
4444 {
4445 	UINT8   ubCnt;
4446 	BOOLEAN fFoundMoney = FALSE;
4447 
4448 	for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
4449 	{
4450 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubCnt];
4451 		//if there is an item here
4452 		if (o->fActive)
4453 		{
4454 			if (GCM->getItem(o->sItemIndex)->getItemClass() != IC_MONEY) return FALSE;
4455 			fFoundMoney = TRUE;
4456 		}
4457 	}
4458 
4459 	Assert(!fFoundMoney || CalculateHowMuchMoneyIsInPlayersOfferArea() > 0);
4460 
4461 	// only return TRUE if there IS money in the POA
4462 	return( fFoundMoney );
4463 }
4464 
4465 
4466 /*
4467 
4468 void MoveRepairEvaluatedPlayerOfferedItemsToArmsDealersOfferArea()
4469 {
4470 	UINT32	uiCnt;
4471 
4472 	//loop through all the slots in the players offer area
4473 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
4474 	{
4475 		//if there is an item here
4476 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[uiCnt];
4477 		if (o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE)
4478 		{
4479 			AddItemToArmsDealerOfferArea(o, -1);
4480 		}
4481 	}
4482 }
4483 
4484 */
4485 
4486 
CalculateHowMuchMoneyIsInPlayersOfferArea()4487 static UINT32 CalculateHowMuchMoneyIsInPlayersOfferArea()
4488 {
4489 	UINT32 total = 0;
4490 	FOR_EACH(INVENTORY_IN_SLOT const, i, PlayersOfferArea)
4491 	{
4492 		INVENTORY_IN_SLOT const& o = *i;
4493 		if (!o.fActive)                                 continue;
4494 		if (GCM->getItem(o.sItemIndex)->getItemClass() != IC_MONEY) continue;
4495 		total += o.ItemObject.uiMoneyAmount;
4496 	}
4497 	return total;
4498 }
4499 
4500 
MovePlayersItemsToBeRepairedToArmsDealersInventory(void)4501 static void MovePlayersItemsToBeRepairedToArmsDealersInventory(void)
4502 {
4503 	//for all items in the dealers items offer area
4504 	UINT32	uiCnt;
4505 
4506 	//loop through all the slots in the shopkeeper's offer area
4507 	for( uiCnt=0; uiCnt<SKI_NUM_TRADING_INV_SLOTS; uiCnt++)
4508 	{
4509 		INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[uiCnt];
4510 		//if there is a item here
4511 		if (a->fActive)
4512 		{
4513 			// NOTE:  Any items that make it into a repairman's dealer offer area are guaranteed to be:
4514 			//   a) Repairable
4515 			//   b) Actually damaged
4516 			//   c) Already stripped of all attachments
4517 			//   d) If a gun, stripped of any non-ammo-class GunAmmoItems, and bullets
4518 
4519 			// add it to the arms dealer's inventory
4520 			GiveObjectToArmsDealerForRepair(gbSelectedArmsDealerID, &a->ItemObject, a->ubIdOfMercWhoOwnsTheItem);
4521 			a->sSpecialItemElement = gubLastSpecialItemAddedAtElement;
4522 
4523 			//Remove the item from the owner merc's inventory
4524 			IfMercOwnedRemoveItemFromMercInv(a);
4525 
4526 			//erase the item from the dealer's offer area
4527 			ClearArmsDealerOfferSlot( uiCnt );
4528 		}
4529 	}
4530 	gfResetShopKeepIdleQuote = TRUE;
4531 }
4532 
4533 
RemoveRepairItemFromDealersOfferArea(INT8 bSlot)4534 static BOOLEAN RemoveRepairItemFromDealersOfferArea(INT8 bSlot)
4535 {
4536 	INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[bSlot];
4537 	//if the item doesn't have a duplicate copy in its owner merc's inventory slot
4538 	if (a->bSlotIdInOtherLocation == -1)
4539 	{
4540 		if (!SKITryToReturnInvToOwnerOrCurrentMerc(a))
4541 		{
4542 			//failed to add item, inventory probably filled up or item is unowned and current merc ineligible
4543 			return( FALSE );
4544 		}
4545 	}
4546 
4547 	// Clear the contents of the dealer's offer slot and its help text
4548 	ClearArmsDealerOfferSlot( bSlot );
4549 
4550 	// Dirty
4551 	fInterfacePanelDirty = DIRTYLEVEL2;
4552 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;
4553 	return( TRUE );
4554 }
4555 
4556 
GetInvSlotOfUnfullMoneyInMercInventory(SOLDIERTYPE * pSoldier)4557 static INT8 GetInvSlotOfUnfullMoneyInMercInventory(SOLDIERTYPE* pSoldier)
4558 {
4559 	UINT8	ubCnt;
4560 
4561 	//loop through the soldier's inventory
4562 	for( ubCnt=0; ubCnt < NUM_INV_SLOTS; ubCnt++)
4563 	{
4564 		// Look for MONEY only, not Gold or Silver!!!  And look for a slot not already full
4565 		if( ( pSoldier->inv[ ubCnt ].usItem == MONEY ) && ( pSoldier->inv[ ubCnt ].uiMoneyAmount < MoneySlotLimit( ubCnt ) ) )
4566 		{
4567 			return( ubCnt );
4568 		}
4569 	}
4570 	return( - 1 );
4571 }
4572 
4573 
ClearArmsDealerOfferSlot(INT32 ubSlotToClear)4574 static void ClearArmsDealerOfferSlot(INT32 ubSlotToClear)
4575 {
4576 	// Clear the contents
4577 	ArmsDealerOfferArea[ ubSlotToClear ] = INVENTORY_IN_SLOT{};
4578 
4579 	//Remove the mouse help text from the region
4580 	gDealersOfferSlotsMouseRegions[ubSlotToClear].SetFastHelpText(ST::null);
4581 
4582 	//if the dealer repairs
4583 	if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
4584 	{
4585 		gDealersOfferSlotsSmallFaceMouseRegions[ubSlotToClear].SetFastHelpText(ST::null);
4586 	}
4587 }
4588 
4589 
4590 static void CheckAndHandleClearingOfPlayerOfferArea(void);
4591 
4592 
ClearPlayersOfferSlot(INT32 ubSlotToClear)4593 static void ClearPlayersOfferSlot(INT32 ubSlotToClear)
4594 {
4595 	// Clear the contents
4596 	PlayersOfferArea[ ubSlotToClear ] = INVENTORY_IN_SLOT{};
4597 
4598 	//Clear the text for the item
4599 	gPlayersOfferSlotsMouseRegions[ubSlotToClear].SetFastHelpText(ST::null);
4600 	gPlayersOfferSlotsSmallFaceMouseRegions[ubSlotToClear].SetFastHelpText(ST::null);
4601 
4602 	// if the player offer area is clear, reset flags for transaction
4603 	CheckAndHandleClearingOfPlayerOfferArea( );
4604 }
4605 
4606 
4607 static BOOLEAN CanShopkeeperOverrideDialogue(void);
4608 
4609 
EvaluateItemAddedToPlayersOfferArea(INT8 bSlotID,BOOLEAN fFirstOne)4610 static void EvaluateItemAddedToPlayersOfferArea(INT8 bSlotID, BOOLEAN fFirstOne)
4611 {
4612 	UINT32  uiEvalResult = EVAL_RESULT_NORMAL;
4613 	BOOLEAN fRocketRifleWasEvaluated = FALSE;
4614 	UINT8   ubNumberOfItemsAddedToRepairDuringThisEvaluation=0;
4615 
4616 	INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bSlotID];
4617 
4618 	// there better be an item there
4619 	Assert(o->fActive);
4620 
4621 	//if money is the item being evaluated, leave
4622 	if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY) return;
4623 
4624 	// if already evaluated, don't do it again
4625 	if (o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED) return;
4626 
4627 	// say "Hmm... Let's see" once per trading session to start evaluation
4628 	// SPECIAL: Devin doesn't have this quote (he's the only one)
4629 	if( !gfDealerHasSaidTheEvaluateQuoteOnceThisSession && ( gbSelectedArmsDealerID != ARMS_DEALER_DEVIN ) )
4630 	{
4631 		gfDealerHasSaidTheEvaluateQuoteOnceThisSession = TRUE;
4632 		StartShopKeeperTalking( SK_QUOTES_PLAYER_REQUESTED_EVALUATION );
4633 	}
4634 
4635 
4636 	//Can this particular kind of item be sold/repaired here
4637 	if (!WillShopKeeperRejectObjectsFromPlayer(gbSelectedArmsDealerID, bSlotID))
4638 	{
4639 		//if the dealer repairs
4640 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
4641 		{
4642 			UINT32 uiNumberOfItemsInForRepairs = CountTotalItemsRepairDealerHasInForRepairs( gbSelectedArmsDealerID );
4643 			UINT32 uiNumberOfItemsAlreadyEvaluated = CountNumberOfItemsInTheArmsDealersOfferArea();
4644 
4645 			//Get the number of items being evaluated
4646 			ubNumberOfItemsAddedToRepairDuringThisEvaluation = o->ItemObject.ubNumberOfObjects;
4647 
4648 			//if there is already enough items in for repairs, complain about it and DON'T accept the item for repairs
4649 			if( ( uiNumberOfItemsAlreadyEvaluated + ubNumberOfItemsAddedToRepairDuringThisEvaluation + uiNumberOfItemsInForRepairs ) > SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME )
4650 			{
4651 				if ( !gfAlreadySaidTooMuchToRepair )
4652 				{
4653 					//Start the dealer talking
4654 					StartShopKeeperTalking( SK_QUOTES_CANT_AFFORD_TO_BUY_OR_TOO_MUCH_TO_REPAIR );
4655 					gfAlreadySaidTooMuchToRepair = TRUE;
4656 				}
4657 				return;
4658 			}
4659 
4660 			//if the item is a rocket rifle
4661 			if (ItemIsARocketRifle(o->sItemIndex)) fRocketRifleWasEvaluated = TRUE;
4662 
4663 			//if the item is damaged, or is a rocket rifle (which always "need repairing" even at 100%, to reset imprinting)
4664 			if (o->ItemObject.bStatus[0] < 100 || fRocketRifleWasEvaluated)
4665 			{
4666 				INT8	bSlotAddedTo;
4667 
4668 				// Move the item to the Dealer's Offer Area
4669 				bSlotAddedTo = AddItemToArmsDealerOfferArea(o, o->bSlotIdInOtherLocation);
4670 
4671 				if( bSlotAddedTo != -1 )
4672 				{
4673 					// Clear the contents
4674 					ClearPlayersOfferSlot( bSlotID );
4675 
4676 					INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[bSlotAddedTo];
4677 
4678 					/* ARM: Leave it there, until transaction occurs it should be recallable
4679 					//Remove the item from the owner merc's inventory
4680 					IfMercOwnedRemoveItemFromMercInv(a);*/
4681 
4682 					//Mark the item as unselected, signifying that it can be moved
4683 					a->uiFlags &= ~ARMS_INV_ITEM_SELECTED;
4684 
4685 					//increment the number of items being added
4686 					ubNumberOfItemsAddedToRepairDuringThisEvaluation++;
4687 
4688 					// check if the item is really badly damaged
4689 					if (GCM->getItem(a->sItemIndex)->getItemClass() != IC_AMMO &&
4690 						a->ItemObject.bStatus[0] < REALLY_BADLY_DAMAGED_THRESHOLD)
4691 					{
4692 						uiEvalResult = EVAL_RESULT_OK_BUT_REALLY_DAMAGED;
4693 					}
4694 
4695 					// check if it's the first time a rocket rifle is being submitted to the electronics guy
4696 					if (fRocketRifleWasEvaluated && GetDealer(gbSelectedArmsDealerID)->hasFlag(ArmsDealerFlag::REPAIRS_ELECTRONICS))
4697 					{
4698 						//if he hasn't yet said his quote
4699 						if( !( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__FREDO_HAS_SAID_ROCKET_RIFLE_QUOTE ) )
4700 						{
4701 							// use this special evaluation result instead (has a unique quote)
4702 							uiEvalResult = EVAL_RESULT_ROCKET_RIFLE;
4703 						}
4704 					}
4705 				}
4706 				else
4707 				{
4708 					SLOGW("Failed to add repair item to ArmsDealerOfferArea.");
4709 					return;
4710 				}
4711 			}
4712 			else
4713 			{
4714 				uiEvalResult = EVAL_RESULT_NOT_DAMAGED;
4715 			}
4716 		}
4717 		else // not a repairman
4718 		{
4719 			uiEvalResult = EvaluateInvSlot(o);
4720 		}
4721 	}
4722 	else // dealer doesn't handle this type of object
4723 	{
4724 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
4725 		{
4726 			// only otherwise repairable items count as actual rejections
4727 			if (GCM->getItem(o->sItemIndex)->getFlags() & ITEM_REPAIRABLE)
4728 			{
4729 				uiEvalResult = EVAL_RESULT_DONT_HANDLE;
4730 			}
4731 			else
4732 			{
4733 				uiEvalResult = EVAL_RESULT_NON_REPAIRABLE;
4734 			}
4735 		}
4736 		else
4737 		{
4738 			uiEvalResult = EVAL_RESULT_DONT_HANDLE;
4739 		}
4740 	}
4741 
4742 	// mark this item as having been evaluated
4743 	o->uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED;
4744 
4745 	// when evaluating complex items that get split into multiple subobjects, dealer
4746 	// will talk only about the first one!
4747 	// don't bother with any of this if shopkeeper can't talk right now
4748 	if ( fFirstOne && CanShopkeeperOverrideDialogue( ) )
4749 	{
4750 		INT16 sQuoteNum = -1;
4751 		UINT8 ubChanceToSayNormalQuote;
4752 
4753 
4754 		switch ( uiEvalResult )
4755 		{
4756 			case EVAL_RESULT_DONT_HANDLE:
4757 				if( SelectedArmsDealer()->type == ARMS_DEALER_SELLS_ONLY )
4758 				{
4759 					// then he doesn't have quotes 17, 19, or 20, always use 4.  Devin doesn't have 18 either,
4760 					// while the text of 18 seems wrong for Sam & Howard if offered something they should consider valuable.
4761 					sQuoteNum = SK_QUOTES_NOT_INTERESTED_IN_THIS_ITEM;
4762 				}
4763 				else
4764 				{
4765 					// he accepts items, but not this one
4766 					sQuoteNum = SK_QUOTES_DURING_EVALUATION_STUFF_REJECTED;
4767 				}
4768 				break;
4769 
4770 			case EVAL_RESULT_NON_REPAIRABLE:
4771 				// use quote 4 for this situation...  It's a bit weird but better than "that looks fine / ain't broke"
4772 				sQuoteNum = SK_QUOTES_NOT_INTERESTED_IN_THIS_ITEM;
4773 				break;
4774 
4775 			case EVAL_RESULT_NOT_DAMAGED:
4776 			case EVAL_RESULT_WORTHLESS:
4777 				sQuoteNum = SK_QUOTES_EVALUATION_RESULT_VALUE_OF_ZERO;
4778 				break;
4779 
4780 			case EVAL_RESULT_OK_BUT_REALLY_DAMAGED:
4781 				sQuoteNum = SK_QUOTES_EVALUATION_RESULT_SOME_REALLY_DAMAGED_ITEMS;
4782 				break;
4783 
4784 			case EVAL_RESULT_ROCKET_RIFLE:
4785 				sQuoteNum = FREDO_QUOTE_CAN_RESET_IMPRINTING;
4786 				break;
4787 
4788 			case EVAL_RESULT_NORMAL:
4789 				//if it has been a long time since saying the last quote
4790 				if( guiLastTimeDealerSaidNormalEvaluationQuote == 0 )
4791 					ubChanceToSayNormalQuote = 100;
4792 				else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 25000 )
4793 					ubChanceToSayNormalQuote = 60;
4794 				else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 12000 )
4795 					ubChanceToSayNormalQuote = 25;
4796 				else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 7000 )
4797 					ubChanceToSayNormalQuote = 10;
4798 				else
4799 					ubChanceToSayNormalQuote = 0;
4800 
4801 				if( Chance( ubChanceToSayNormalQuote ) )
4802 				{
4803 					sQuoteNum = SK_QUOTES_EVALUATION_RESULT_NORMAL;
4804 				}
4805 				break;
4806 
4807 			default:
4808 				SLOGW("Invalid evaluation result of %d.", uiEvalResult );
4809 				break;
4810 		}
4811 
4812 		// if a valid quote was selected
4813 		if ( sQuoteNum != -1 )
4814 		{
4815 			// if the appropriate quote hasn't already been used during this evaluation cycle
4816 			if ( !gfEvalResultQuoteSaid[ uiEvalResult ] )
4817 			{
4818 				// try to say it
4819 				if ( StartShopKeeperTalking( ( UINT16 ) sQuoteNum ) )
4820 				{
4821 					// set flag to keep from repeating it
4822 					gfEvalResultQuoteSaid[ uiEvalResult ] = TRUE;
4823 
4824 					// if it's the normal result
4825 					if ( uiEvalResult == EVAL_RESULT_NORMAL )
4826 					{
4827 						// set new time of last usage
4828 						guiLastTimeDealerSaidNormalEvaluationQuote = GetJA2Clock();
4829 					}
4830 
4831 					if ( sQuoteNum == FREDO_QUOTE_CAN_RESET_IMPRINTING )
4832 					{
4833 						//Set the fact that we have said this (to prevent that quote from coming up again)
4834 						gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__FREDO_HAS_SAID_ROCKET_RIFLE_QUOTE;
4835 					}
4836 				}
4837 			}
4838 		}
4839 	}
4840 }
4841 
4842 
DoSkiMessageBox(const ST::string & str,ScreenID uiExitScreen,MessageBoxFlags ubFlags,MSGBOX_CALLBACK ReturnCallback)4843 void DoSkiMessageBox(const ST::string& str, ScreenID uiExitScreen, MessageBoxFlags ubFlags, MSGBOX_CALLBACK ReturnCallback)
4844 {
4845 	// reset exit mode
4846 	gfExitSKIDueToMessageBox = TRUE;
4847 
4848 	// do message box and return
4849 	SGPBox const centering_rect = { 0, 0, SCREEN_WIDTH, 339 };
4850 	DoMessageBox(MSG_BOX_BASIC_STYLE, str, uiExitScreen, ubFlags, ReturnCallback, &centering_rect);
4851 }
4852 
4853 
ConfirmDontHaveEnoughForTheDealerMessageBoxCallBack(MessageBoxReturnValue const bExitValue)4854 void ConfirmDontHaveEnoughForTheDealerMessageBoxCallBack(MessageBoxReturnValue const bExitValue)
4855 {
4856 	// simply redraw the panel
4857 	if( bExitValue == MSG_BOX_RETURN_OK )
4858 	{
4859 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
4860 	}
4861 
4862 	// done, re-enable calls to PerformTransaction()
4863 	gfPerformTransactionInProgress = FALSE;
4864 }
4865 
4866 
4867 
ConfirmToDeductMoneyFromPlayersAccountMessageBoxCallBack(MessageBoxReturnValue const bExitValue)4868 void ConfirmToDeductMoneyFromPlayersAccountMessageBoxCallBack(MessageBoxReturnValue const bExitValue)
4869 {
4870 	// yes, deduct the money
4871 	if( bExitValue == MSG_BOX_RETURN_YES )
4872 	{
4873 		UINT32 uiPlayersOfferAreaValue = CalculateTotalPlayersValue();
4874 		UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost();
4875 		INT32  iMoneyToDeduct = (INT32)( uiArmsDealersItemsCost - uiPlayersOfferAreaValue );
4876 
4877 		//Perform the transaction with the extra money from the players account
4878 		PerformTransaction( iMoneyToDeduct );
4879 
4880 		AddTransactionToPlayersBook( PURCHASED_ITEM_FROM_DEALER, SelectedArmsDealer()->profileID, GetWorldTotalMin(), -iMoneyToDeduct );
4881 	}
4882 
4883 	// done, re-enable calls to PerformTransaction()
4884 	gfPerformTransactionInProgress = FALSE;
4885 
4886 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
4887 }
4888 
4889 
4890 // run through what the player has on the table and see if the shop keep will accept it or not
WillShopKeeperRejectObjectsFromPlayer(INT8 bDealerId,INT8 bSlotId)4891 static BOOLEAN WillShopKeeperRejectObjectsFromPlayer(INT8 bDealerId, INT8 bSlotId)
4892 {
4893 	BOOLEAN fRejected = TRUE;
4894 
4895 	const INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bSlotId];
4896 	if (GCM->getItem(o->sItemIndex)->getItemClass() == IC_MONEY)
4897 	{
4898 		fRejected = FALSE;
4899 	}
4900 	else if (CanDealerTransactItem(gbSelectedArmsDealerID, o->sItemIndex, TRUE))
4901 	{
4902 		fRejected = FALSE;
4903 	}
4904 
4905 	else
4906 	{
4907 		fRejected = TRUE;
4908 	}
4909 
4910 	return( fRejected );
4911 }
4912 
4913 
CheckAndHandleClearingOfPlayerOfferArea(void)4914 static void CheckAndHandleClearingOfPlayerOfferArea(void)
4915 {
4916 	INT32 iCounter = 0;
4917 	BOOLEAN fActiveSlot = FALSE;
4918 
4919 	// find out if all the player trade slots/offer area is empty
4920 	for( iCounter=0; iCounter<SKI_NUM_TRADING_INV_SLOTS; iCounter++)
4921 	{
4922 		if (PlayersOfferArea[iCounter].fActive)
4923 		{
4924 			// nope, there is an active slot
4925 			fActiveSlot = TRUE;
4926 		}
4927 	}
4928 
4929 	// if all player's offer area slots are empty
4930 	if (!fActiveSlot)
4931 	{
4932 		ResetAllQuoteSaidFlags();
4933 		gfRemindedPlayerToPickUpHisStuff = FALSE;
4934 	}
4935 }
4936 
4937 
CanShopkeeperOverrideDialogue(void)4938 static BOOLEAN CanShopkeeperOverrideDialogue(void)
4939 {
4940 	// if he's not currently saying anything
4941 	if( !gfIsTheShopKeeperTalking && ( giShopKeepDialogueEventinProgress == -1 ) )
4942 	{
4943 		return( TRUE );
4944 	}
4945 
4946 	// if the quote he is currently saying is unimportant (fluff),
4947 	// then shut him up & allow the override
4948 	if( giShopKeepDialogueEventinProgress < SK_QUOTES_PLAYER_REQUESTED_EVALUATION )
4949 	{
4950 		// shutup the shop keep and do this quote
4951 		ShutUpShopKeeper( );
4952 		return( TRUE );
4953 	}
4954 
4955 	// he's currently saying something important
4956 	return( FALSE );
4957 }
4958 
4959 
CrossOutUnwantedItems(void)4960 static void CrossOutUnwantedItems(void)
4961 {
4962 	INT8  bSlotId = 0;
4963 	INT16 sBoxStartX = 0, sBoxStartY = 0;
4964 	INT16 sBoxWidth = 0, sBoxHeight = 0;
4965 
4966 	// get the box height and width
4967 	sBoxWidth  = SKI_INV_SLOT_WIDTH;
4968 	sBoxHeight = SKI_INV_SLOT_HEIGHT;
4969 
4970 	for( bSlotId = 0; bSlotId < SKI_NUM_TRADING_INV_SLOTS; bSlotId++ )
4971 	{
4972 		const INVENTORY_IN_SLOT* const o = &PlayersOfferArea[bSlotId];
4973 		// now run through what's on the players offer area
4974 		if (o->fActive)
4975 		{
4976 			// skip purchased items!
4977 			if (!(o->uiFlags & ARMS_INV_JUST_PURCHASED))
4978 			{
4979 				//If item can't be sold here, or it's completely worthless (very cheap / very broken)
4980 				if (WillShopKeeperRejectObjectsFromPlayer(gbSelectedArmsDealerID, bSlotId) ||
4981 						!(o->uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE))
4982 				{
4983 					// get x and y positions
4984 					sBoxStartX = SKI_PLAYERS_TRADING_INV_X + ( bSlotId % SKI_NUM_TRADING_INV_COLS ) * ( SKI_INV_OFFSET_X );
4985 					sBoxStartY = SKI_PLAYERS_TRADING_INV_Y + ( bSlotId / SKI_NUM_TRADING_INV_COLS ) * ( SKI_INV_OFFSET_Y );
4986 
4987 					BltVideoObject(FRAME_BUFFER, guiItemCrossOut, 0, sBoxStartX + 22, sBoxStartY);
4988 
4989 					// invalidate the region
4990 					InvalidateRegion(sBoxStartX - 1, sBoxStartY - 1, sBoxStartX + sBoxWidth + 1, sBoxStartY + sBoxHeight + 1 );
4991 				}
4992 			}
4993 		}
4994 	}
4995 }
4996 
4997 
HandleCheckIfEnoughOnTheTable(void)4998 static void HandleCheckIfEnoughOnTheTable(void)
4999 {
5000 	static INT32 iLastTime = 0;
5001 	INT32  iDifference = 0, iRand = 0;
5002 	UINT32 uiPlayersOfferAreaValue = CalculateTotalPlayersValue();
5003 	UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost();
5004 
5005 	if( ( iLastTime == 0 ) || gfResetShopKeepIdleQuote )
5006 	{
5007 		iLastTime = GetJA2Clock();
5008 		gfResetShopKeepIdleQuote = FALSE;
5009 	}
5010 
5011 	iDifference = GetJA2Clock() - iLastTime;
5012 
5013 	iRand = Random( 100 );
5014 
5015 	// delay for shopkeeper passed?
5016 	if( iDifference > DELAY_FOR_SHOPKEEPER_IDLE_QUOTE )
5017 	{
5018 		// random chance enough?
5019 		if( iRand > CHANCE_FOR_SHOPKEEPER_IDLE_QUOTE )
5020 		{
5021 			// is there enough on the table
5022 			if( ( uiArmsDealersItemsCost > uiPlayersOfferAreaValue ) && ( uiPlayersOfferAreaValue ) )
5023 			{
5024 				StartShopKeeperTalking( SK_QUOTES_EVAULATION_PLAYER_DOESNT_HAVE_ENOUGH_VALUE );
5025 			}
5026 		}
5027 
5028 		gfResetShopKeepIdleQuote = TRUE;
5029 	}
5030 }
5031 
5032 
InitShopKeeperItemDescBox(OBJECTTYPE * pObject,UINT8 ubPocket,UINT8 ubFromLocation)5033 static void InitShopKeeperItemDescBox(OBJECTTYPE* pObject, UINT8 ubPocket, UINT8 ubFromLocation)
5034 {
5035 	INT16 sPosX, sPosY;
5036 
5037 
5038 	switch( ubFromLocation )
5039 	{
5040 		case ARMS_DEALER_INVENTORY:
5041 		{
5042 			UINT8	ubSelectedInvSlot = ubPocket - gSelectArmsDealerInfo.ubFirstItemIndexOnPage;
5043 
5044 			sPosX = SKI_ARMS_DEALERS_INV_START_X + ( SKI_INV_OFFSET_X * ( ubSelectedInvSlot % SKI_NUM_ARMS_DEALERS_INV_COLS ) - ( 358 / 2 ) ) + SKI_INV_SLOT_WIDTH / 2;
5045 
5046 			sPosY = SKI_ARMS_DEALERS_INV_START_Y + ( ( SKI_INV_OFFSET_Y * ubSelectedInvSlot / SKI_NUM_ARMS_DEALERS_INV_COLS ) + 1 ) - ( 128 / 2 ) + SKI_INV_SLOT_HEIGHT / 2;
5047 
5048 			//if the start position + the height of the box is off the screen, reposition
5049 			if( sPosY < 0 )
5050 				sPosY = 0;
5051 
5052 
5053 			//if the start position + the width of the box is off the screen, reposition
5054 			if (sPosX + 358 > SCREEN_WIDTH)
5055 				sPosX = SCREEN_WIDTH - 358 - 5;
5056 
5057 			//if it is starting to far to the left
5058 			else if( sPosX < 0 )
5059 				sPosX = 0;
5060 
5061 			//if the box will appear over the mercs face, move the box over so it doesn't obstruct the face
5062 			if( sPosY < SKI_FACE_Y + SKI_FACE_HEIGHT + 20 )
5063 				if( sPosX < 160 )
5064 					sPosX = 160;
5065 
5066 		}
5067 		break;
5068 
5069 		case ARMS_DEALER_OFFER_AREA:
5070 		{
5071 			sPosX = SKI_ARMS_DEALERS_TRADING_INV_X + ( SKI_INV_OFFSET_X * ( ubPocket % ( SKI_NUM_TRADING_INV_SLOTS/2) ) - ( 358 / 2 ) ) + SKI_INV_SLOT_WIDTH / 2;
5072 
5073 			sPosY = SKI_ARMS_DEALERS_TRADING_INV_Y + ( ( SKI_INV_OFFSET_Y * ubPocket / ( SKI_NUM_TRADING_INV_SLOTS/2) ) + 1 ) - ( 128 / 2 ) + SKI_INV_SLOT_HEIGHT / 2;
5074 
5075 			//if the start position + the height of the box is off the screen, reposition
5076 			if( sPosY < 0 )
5077 				sPosY = 0;
5078 
5079 
5080 			//if the start position + the width of the box is off the screen, reposition
5081 			if (sPosX + 358 > SCREEN_WIDTH)
5082 				sPosX = SCREEN_WIDTH - 358 - 5;
5083 
5084 			//if it is starting to far to the left
5085 			else if( sPosX < 0 )
5086 				sPosX = 10;
5087 
5088 			//if the box will appear over the mercs face, move the box over so it doesn't obstruct the face
5089 			if( sPosY < SKI_FACE_Y + SKI_FACE_HEIGHT + 20 )
5090 				if( sPosX < 160 )
5091 					sPosY = 140;
5092 		}
5093 		break;
5094 
5095 		default:
5096 			SLOGA("InitShopKeeperItemDescBox: invalid FromLocation");
5097 			return;
5098 	}
5099 
5100 
5101 	pShopKeeperItemDescObject = pObject;
5102 
5103 	InitItemDescriptionBox( gpSMCurrentMerc, 255, sPosX, sPosY, 0 );
5104 
5105 	StartSKIDescriptionBox( );
5106 }
5107 
5108 
StartSKIDescriptionBox(void)5109 void StartSKIDescriptionBox(void)
5110 {
5111 	INT32 iCnt;
5112 
5113 	//if the current merc is too far away, dont shade the SM panel because it is already shaded
5114 	const UINT16 h = (gfSMDisableForItems ? INV_INTERFACE_START_Y : SCREEN_HEIGHT);
5115 	DrawHatchOnInventory(FRAME_BUFFER, 0, 0, SCREEN_WIDTH, h);
5116 
5117 	InvalidateScreen();
5118 
5119 	// disable almost everything!
5120 
5121 	gfSMDisableForItems = TRUE;
5122 	DisableInvRegions( gfSMDisableForItems );
5123 
5124 	DisableButton( guiSKI_InvPageUpButton );
5125 	DisableButton( guiSKI_InvPageDownButton );
5126 	DisableButton( guiSKI_TransactionButton );
5127 	DisableButton( guiSKI_DoneButton );
5128 
5129 	DisableAllDealersInventorySlots();
5130 	DisableAllDealersOfferSlots();
5131 
5132 	for (iCnt = 0; iCnt < SKI_NUM_TRADING_INV_SLOTS; iCnt++)
5133 	{
5134 		gPlayersOfferSlotsMouseRegions[iCnt].Disable();
5135 		gPlayersOfferSlotsSmallFaceMouseRegions[iCnt].Disable();
5136 	}
5137 
5138 	if( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS )
5139 	{
5140 		gShopKeeperSubTitleMouseRegion.Disable();
5141 	}
5142 
5143 	RenderItemDescriptionBox( );
5144 }
5145 
5146 
DeleteShopKeeperItemDescBox()5147 void DeleteShopKeeperItemDescBox()
5148 {
5149 	INT32 iCnt;
5150 
5151 
5152 	pShopKeeperItemDescObject = NULL;
5153 	gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
5154 
5155 	//Redraw the face
5156 	giShopKeeperFaceIndex->uiFlags |= FACE_REDRAW_WHOLE_FACE_NEXT_FRAME;
5157 
5158 	// enable almost everything!
5159 
5160 	CheckForDisabledForGiveItem( );
5161 	DisableInvRegions( gfSMDisableForItems );	//actually does an enable if current merc is in range
5162 
5163 	EnableDisableDealersInventoryPageButtons();
5164 	EnableDisableEvaluateAndTransactionButtons();
5165 
5166 	EnableAllDealersInventorySlots();
5167 	EnableAllDealersOfferSlots();
5168 
5169 	for (iCnt = 0; iCnt < SKI_NUM_TRADING_INV_SLOTS; iCnt++)
5170 	{
5171 		gPlayersOfferSlotsMouseRegions[iCnt].Enable();
5172 		gPlayersOfferSlotsSmallFaceMouseRegions[iCnt].Enable();
5173 	}
5174 
5175 	if( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS )
5176 	{
5177 		gShopKeeperSubTitleMouseRegion.Enable();
5178 	}
5179 }
5180 
5181 
5182 static BOOLEAN AddObjectForEvaluation(OBJECTTYPE* pObject, UINT8 ubOwnerProfileId, INT8 bOwnerSlotId, BOOLEAN fFirstOne);
5183 static void CountSubObjectsInObject(OBJECTTYPE* pComplexObject, UINT8* pubTotalSubObjects, UINT8* pubRepairableSubObjects, UINT8* pubNonRepairableSubObjects);
5184 static void SplitComplexObjectIntoSubObjects(OBJECTTYPE* pComplexObject);
5185 
5186 
OfferObjectToDealer(OBJECTTYPE * pComplexObject,UINT8 ubOwnerProfileId,INT8 bOwnerSlotId)5187 static BOOLEAN OfferObjectToDealer(OBJECTTYPE* pComplexObject, UINT8 ubOwnerProfileId, INT8 bOwnerSlotId)
5188 {
5189 	UINT8   ubTotalSubObjects;
5190 	UINT8   ubRepairableSubObjects;
5191 	UINT8   ubNonRepairableSubObjects;
5192 	UINT8   ubDealerOfferAreaSlotsNeeded;
5193 	UINT8   ubPlayerOfferAreaSlotsNeeded;
5194 	UINT8   ubDiff;
5195 	UINT8   ubHowManyMoreItemsCanDealerTake;
5196 	UINT8   ubSubObject;
5197 	BOOLEAN fFirstOne = TRUE;
5198 	BOOLEAN fSuccess = FALSE;
5199 
5200 
5201 	if (!DoesDealerDoRepairs(gbSelectedArmsDealerID))
5202 	{
5203 		// if not actually doing repairs, there's no need to split objects up at all
5204 		if ( !AddObjectForEvaluation( pComplexObject, ubOwnerProfileId, bOwnerSlotId, TRUE ) )
5205 		{
5206 			gfDisplayNoRoomMsg = TRUE;
5207 			return(FALSE);	// no room
5208 		}
5209 	}
5210 	else // dealing with a repairman
5211 	{
5212 		// split the object into its components
5213 		SplitComplexObjectIntoSubObjects( pComplexObject );
5214 
5215 		// determine how many subobjects of each category this complex object will have to be broken up into
5216 		CountSubObjectsInObject( pComplexObject, &ubTotalSubObjects, &ubRepairableSubObjects, &ubNonRepairableSubObjects );
5217 
5218 		// in the simplest situation, the # subobjects of each type gives us how many slots of each type are needed
5219 		ubDealerOfferAreaSlotsNeeded = ubRepairableSubObjects;
5220 		ubPlayerOfferAreaSlotsNeeded = ubNonRepairableSubObjects;
5221 
5222 		// consider that if dealer is at or will reach his max # of items repaired limit, not everything repairable will move
5223 		ubHowManyMoreItemsCanDealerTake = SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME -
5224 																			CountTotalItemsRepairDealerHasInForRepairs( gbSelectedArmsDealerID ) -
5225 																			CountNumberOfItemsInTheArmsDealersOfferArea();
5226 
5227 		// if he can't repair everything repairable that we're about to submit, the space we'll need changes
5228 		if ( ubDealerOfferAreaSlotsNeeded > ubHowManyMoreItemsCanDealerTake )
5229 		{
5230 			ubDiff = ubDealerOfferAreaSlotsNeeded - ubHowManyMoreItemsCanDealerTake;
5231 			ubDealerOfferAreaSlotsNeeded -= ubDiff;
5232 			ubPlayerOfferAreaSlotsNeeded += ubDiff;
5233 		}
5234 
5235 		// if there is anything repairable that dealer will accept
5236 		if ( ubDealerOfferAreaSlotsNeeded > 0 )
5237 		{
5238 			// we need at least one EXTRA empty slot in player's area to pass repairable objects through for their evaluation
5239 			ubPlayerOfferAreaSlotsNeeded++;
5240 		}
5241 
5242 		Assert( SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME < SKI_NUM_TRADING_INV_SLOTS );
5243 		/*
5244 		This code is commented out because a repair dealer will never be allowed to repair over more then
5245 		SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME ( 4 ) items at a time, therefore, cant fill up the dealer offer area
5246 
5247 		// check if we have room for all of each type
5248 		if ( ( ubDealerOfferAreaSlotsNeeded + CountNumberOfItemsInTheArmsDealersOfferArea() ) > SKI_NUM_TRADING_INV_SLOTS )
5249 		{
5250 			// tell player there's not enough room in the dealer's offer area
5251 			DoSkiMessageBox("There is not enough room in the dealers repair area.", SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, NULL);
5252 			return(FALSE);	// no room
5253 		}*/
5254 
5255 		if ( ( ubPlayerOfferAreaSlotsNeeded + CountNumberOfItemsInThePlayersOfferArea( ) ) > SKI_NUM_TRADING_INV_SLOTS )
5256 		{
5257 			gfDisplayNoRoomMsg = TRUE;
5258 			return(FALSE);	// no room
5259 		}
5260 
5261 		// we have room, so move them all to the appropriate slots
5262 		for ( ubSubObject = 0; ubSubObject < MAX_SUBOBJECTS_PER_OBJECT; ubSubObject++ )
5263 		{
5264 			// if there is something stored there
5265 			if ( gSubObject[ ubSubObject ].usItem != NONE )
5266 			{
5267 				// if it's the main item itself (always in the very first subobject),
5268 				// and it has no other subobjects
5269 				if ( ( ubSubObject == 0 ) && ( ubTotalSubObjects == 1) )
5270 				{
5271 					// store its owner merc as the owner, and store the correct slot
5272 					fSuccess = AddObjectForEvaluation( &gSubObject[ ubSubObject ], ubOwnerProfileId, bOwnerSlotId, fFirstOne );
5273 				}
5274 				else	// attachments, bullets/payload
5275 				{
5276 					// store it with a valid owner, but an invalid slot, so it still shows
5277 					// who owns it, but can't return to its slot
5278 					// ARM: New code will be needed here if we add parent/child item
5279 					// support & interface
5280 					fSuccess = AddObjectForEvaluation( &gSubObject[ ubSubObject ], ubOwnerProfileId, -1, fFirstOne );
5281 				}
5282 
5283 				// it has to succeed, or we have a bug in our earlier check for sufficient room
5284 				Assert( fSuccess );
5285 
5286 				fFirstOne = FALSE;
5287 			}
5288 		}
5289 
5290 		gfAlreadySaidTooMuchToRepair = FALSE;
5291 	}
5292 
5293 	//ARM: This comment isn't true unless parent/child support is added.  Right now repairmen don't do this!
5294 	// NOTE that, either way, if owned, the item remains in the merc's inventory (hatched out) until the transaction is completed
5295 
5296 	// Dirty
5297 	fInterfacePanelDirty = DIRTYLEVEL2;
5298 
5299 	return(TRUE); // it worked
5300 }
5301 
5302 
SplitComplexObjectIntoSubObjects(OBJECTTYPE * pComplexObject)5303 static void SplitComplexObjectIntoSubObjects(OBJECTTYPE* pComplexObject)
5304 {
5305 	OBJECTTYPE *pNextObj = &gSubObject[ 0 ];
5306 	UINT8 ubNextFreeSlot = 0;
5307 	UINT8 ubCnt;
5308 
5309 
5310 	Assert( pComplexObject );
5311 	Assert( pComplexObject->ubNumberOfObjects > 0 );
5312 	Assert( pComplexObject->ubNumberOfObjects <= MAX_OBJECTS_PER_SLOT );
5313 
5314 
5315 	// clear subobject array
5316 	std::fill_n(gSubObject, MAX_SUBOBJECTS_PER_OBJECT, OBJECTTYPE{});
5317 
5318 
5319 	// if it isn't stacked
5320 	if ( pComplexObject->ubNumberOfObjects == 1 )
5321 	{
5322 		// make the main item into the very first subobject
5323 		*pNextObj = *pComplexObject;
5324 
5325 		// strip off any loaded ammo/payload
5326 		if ( GCM->getItem(pComplexObject->usItem)->getItemClass() == IC_GUN )
5327 		{
5328 			// Exception: don't do this with rocket launchers, their "shots left" are fake and this screws 'em up!
5329 			if ( pComplexObject->usItem != ROCKET_LAUNCHER )
5330 			{
5331 				pNextObj->ubGunShotsLeft = 0;
5332 				pNextObj->usGunAmmoItem = NONE;
5333 			}
5334 
5335 			/* gunAmmoStatus is currently not being used that way, it's strictly used as a jammed/unjammed, and so should never be 0
5336 			// if jammed, must remember that, so leave it
5337 			if ( pNextObj->bGunAmmoStatus > 0 )
5338 			{
5339 				pNextObj->bGunAmmoStatus = 0;
5340 			}*/
5341 		}
5342 
5343 		/* ARM: Can now repair with removeable attachments still attached...
5344 		// strip off any seperable attachments
5345 		for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
5346 		{
5347 			if ( pComplexObject->usAttachItem[ ubCnt ] != NONE )
5348 			{
5349 				// If the attachment is detachable
5350 				if (! (GCM->getItem(pComplexObject->usAttachItem[ ubCnt ])->getFlags() & ITEM_INSEPARABLE ) )
5351 				{
5352 					pNextObj->usAttachItem[ ubCnt ] = NONE;
5353 					pNextObj->bAttachStatus[ ubCnt ] = 0;
5354 				}
5355 			}
5356 		}*/
5357 
5358 		// advance to next available subobject
5359 		pNextObj = &gSubObject[ ++ubNextFreeSlot ];
5360 
5361 
5362 		// if it's a gun
5363 		if ( GCM->getItem(pComplexObject->usItem)->getItemClass() == IC_GUN )
5364 		{
5365 			// and it has ammo/payload
5366 			if ( pComplexObject->usGunAmmoItem != NONE )
5367 			{
5368 				// if it's bullets
5369 				if ( GCM->getItem(pComplexObject->usGunAmmoItem)->getItemClass() == IC_AMMO )
5370 				{
5371 					// and there are some left
5372 					if ( pComplexObject->ubGunShotsLeft > 0 )
5373 					{
5374 						// make the bullets into another subobject
5375 						CreateItem( pComplexObject->usGunAmmoItem, 100, pNextObj );
5376 						// set how many are left
5377 						pNextObj->bStatus[ 0 ] = pComplexObject->ubGunShotsLeft;
5378 
5379 						pNextObj = &gSubObject[ ++ubNextFreeSlot ];
5380 					}
5381 					// ignore this if it's out of bullets
5382 				}
5383 				else // non-ammo payload
5384 				{
5385 					// make the payload into another subobject
5386 					CreateItem( pComplexObject->usGunAmmoItem, pComplexObject->bGunAmmoStatus, pNextObj );
5387 
5388 					// if the gun was jammed, fix up the payload's status
5389 					if ( pNextObj->bStatus[ 0 ] < 0 )
5390 					{
5391 						pNextObj->bStatus[ 0 ] *= -1;
5392 					}
5393 
5394 					pNextObj = &gSubObject[ ++ubNextFreeSlot ];
5395 				}
5396 			}
5397 		}
5398 
5399 
5400 		/* ARM: Can now repair with removeable attachments still attached...
5401 		// make each detachable attachment into a separate subobject
5402 		for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
5403 		{
5404 			if ( pComplexObject->usAttachItem[ ubCnt ] != NONE )
5405 			{
5406 				// If the attachment is detachable
5407 				if (! (GCM->getItem(pComplexObject->usAttachItem[ ubCnt ])->getFlags() & ITEM_INSEPARABLE ) )
5408 				{
5409 					CreateItem( pComplexObject->usAttachItem[ ubCnt ], pComplexObject->bAttachStatus[ ubCnt ], pNextObj );
5410 
5411 					pNextObj = &gSubObject[ ++ubNextFreeSlot ];
5412 				}
5413 			}
5414 		}*/
5415 	}
5416 	else // stacked
5417 	{
5418 		// these can't be guns, can't have any attachments, can't be imprinted, etc.
5419 		Assert ( GCM->getItem(pComplexObject->usItem)->getItemClass() != IC_GUN );
5420 
5421 		for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ )
5422 		{
5423 			Assert( pComplexObject->usAttachItem[ ubCnt ] == NONE );
5424 		}
5425 
5426 		// make each item in the stack into a separate subobject
5427 		for( ubCnt = 0; ubCnt < pComplexObject->ubNumberOfObjects; ubCnt++ )
5428 		{
5429 			CreateItem( pComplexObject->usItem, pComplexObject->bStatus[ ubCnt ], pNextObj );
5430 
5431 			// advance to next available subobject
5432 			pNextObj = &gSubObject[ ++ubNextFreeSlot ];
5433 		}
5434 	}
5435 
5436 	// make sure we didn't screw up and honk something!
5437 	Assert( ubNextFreeSlot <= MAX_SUBOBJECTS_PER_OBJECT );
5438 }
5439 
5440 
CountSubObjectsInObject(OBJECTTYPE * pComplexObject,UINT8 * pubTotalSubObjects,UINT8 * pubRepairableSubObjects,UINT8 * pubNonRepairableSubObjects)5441 static void CountSubObjectsInObject(OBJECTTYPE* pComplexObject, UINT8* pubTotalSubObjects, UINT8* pubRepairableSubObjects, UINT8* pubNonRepairableSubObjects)
5442 {
5443 	UINT8 ubSubObject;
5444 
5445 	*pubTotalSubObjects = 0;
5446 	*pubRepairableSubObjects = 0;
5447 	*pubNonRepairableSubObjects = 0;
5448 
5449 	// check every subobject and count it as either repairable or non-
5450 	for ( ubSubObject = 0; ubSubObject < MAX_SUBOBJECTS_PER_OBJECT; ubSubObject++ )
5451 	{
5452 		// if there is something stored there
5453 		if ( gSubObject[ ubSubObject ].usItem != NONE )
5454 		{
5455 			( *pubTotalSubObjects )++;
5456 
5457 			// is it in need of fixing, and also repairable by this dealer?
5458 			// A jammed gun with a 100% status is NOT repairable - shouldn't ever happen
5459 			if ( ( gSubObject[ ubSubObject ].bStatus[ 0 ] != 100 ) &&
5460 				CanDealerRepairItem( gbSelectedArmsDealerID, gSubObject[ ubSubObject ].usItem ) )
5461 
5462 			{
5463 				( *pubRepairableSubObjects )++;
5464 			}
5465 			else
5466 			{
5467 				( *pubNonRepairableSubObjects )++;
5468 			}
5469 		}
5470 	}
5471 }
5472 
5473 
AddObjectForEvaluation(OBJECTTYPE * pObject,UINT8 ubOwnerProfileId,INT8 bOwnerSlotId,BOOLEAN fFirstOne)5474 static BOOLEAN AddObjectForEvaluation(OBJECTTYPE* pObject, UINT8 ubOwnerProfileId, INT8 bOwnerSlotId, BOOLEAN fFirstOne)
5475 {
5476 	INVENTORY_IN_SLOT InvSlot;
5477 	INT8 bAddedToSlotID;
5478 
5479 	// Make a new inv slot out of the subobject
5480 	InvSlot = INVENTORY_IN_SLOT{};
5481 	InvSlot.ItemObject = *pObject;
5482 
5483 	InvSlot.sItemIndex = pObject->usItem;
5484 	InvSlot.ubLocationOfObject = PLAYERS_INVENTORY;
5485 	InvSlot.ubIdOfMercWhoOwnsTheItem = ubOwnerProfileId;
5486 
5487 	//Add the item to the Players Offer Area
5488 	bAddedToSlotID = AddItemToPlayersOfferArea( ubOwnerProfileId, &InvSlot, bOwnerSlotId );
5489 
5490 	//Do the evaluation for the item if it was added correctly
5491 	if( bAddedToSlotID != -1 )
5492 	{
5493 		// This will move any repairable (sub)objects into the dealer's offer area immediately
5494 		EvaluateItemAddedToPlayersOfferArea( bAddedToSlotID, fFirstOne );
5495 		return( TRUE );
5496 	}
5497 	else
5498 	{
5499 		return( FALSE );
5500 	}
5501 }
5502 
5503 
5504 // The Shopkeeper interface *MUST* use this intermediary function instead of calling AutoPlaceObject() directly!
5505 // This is because the OBJECTTYPEs used within Shopkeeper may contain an illegal ubNumberOfObjects
ShopkeeperAutoPlaceObject(SOLDIERTYPE * pSoldier,OBJECTTYPE * pObject,BOOLEAN fNewItem)5506 static BOOLEAN ShopkeeperAutoPlaceObject(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObject, BOOLEAN fNewItem)
5507 {
5508 	OBJECTTYPE CopyOfObject;
5509 	UINT8 ubObjectsLeftToPlace;
5510 
5511 	// the entire pObj will get memset to 0 by RemoveObjs() if all the items are successfully placed,
5512 	// so we have to keep a copy to retrieve with every iteration of the loop
5513 	CopyOfObject = *pObject;
5514 
5515 
5516 	ubObjectsLeftToPlace = pObject->ubNumberOfObjects;
5517 
5518 	while ( ubObjectsLeftToPlace > 0 )
5519 	{
5520 		// figure out how many to place during this loop iteration.  Can't do more than MAX_OBJECTS_PER_SLOT at a time
5521 		pObject->ubNumberOfObjects = MIN( MAX_OBJECTS_PER_SLOT, ubObjectsLeftToPlace);
5522 		ubObjectsLeftToPlace -= pObject->ubNumberOfObjects;
5523 
5524 		if (!AutoPlaceObject( pSoldier, pObject, fNewItem ))
5525 		{
5526 			// no more room, didn't all fit - add back in any that we didn't even get to yet
5527 			pObject->ubNumberOfObjects += ubObjectsLeftToPlace;
5528 			return( FALSE );
5529 		}
5530 
5531 		// restore object properties from our backup copy
5532 		*pObject = CopyOfObject;
5533 	}
5534 
5535 	return( TRUE );
5536 }
5537 
5538 
5539 // The Shopkeeper interface *MUST* use this intermediary function instead of calling AddItemToPool() directly!
5540 // This is because the OBJECTTYPEs used within Shopkeeper may contain an illegal ubNumberOfObjects
ShopkeeperAddItemToPool(INT16 const sGridNo,OBJECTTYPE * const pObject,UINT8 const ubLevel)5541 static void ShopkeeperAddItemToPool(INT16 const sGridNo, OBJECTTYPE* const pObject, UINT8 const ubLevel)
5542 {
5543 	OBJECTTYPE CopyOfObject;
5544 	UINT8 ubObjectsLeftToPlace;
5545 
5546 	// the entire pObj will get memset to 0 by RemoveObjs() if all the items are successfully placed,
5547 	// so we have to keep a copy to retrieve with every iteration of the loop
5548 	CopyOfObject = *pObject;
5549 
5550 	ubObjectsLeftToPlace = pObject->ubNumberOfObjects;
5551 
5552 	while ( ubObjectsLeftToPlace > 0 )
5553 	{
5554 		// figure out how many to place during this loop iteration.  Can't do more than MAX_OBJECTS_PER_SLOT at a time
5555 		pObject->ubNumberOfObjects = MIN( MAX_OBJECTS_PER_SLOT, ubObjectsLeftToPlace);
5556 		ubObjectsLeftToPlace -= pObject->ubNumberOfObjects;
5557 
5558 		AddItemToPool(sGridNo, pObject, VISIBLE, ubLevel, 0, 0);
5559 
5560 		// restore object properties from our backup copy
5561 		*pObject = CopyOfObject;
5562 	}
5563 }
5564 
5565 
IfMercOwnedCopyItemToMercInv(const INVENTORY_IN_SLOT * pInv)5566 static void IfMercOwnedCopyItemToMercInv(const INVENTORY_IN_SLOT* pInv)
5567 {
5568 	//if the item picked up was in a previous location, and that location is on a merc's inventory
5569 	if ( ( pInv->bSlotIdInOtherLocation != -1 ) && ( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) )
5570 	{
5571 		// then it better be a valid slot #
5572 		Assert( pInv->bSlotIdInOtherLocation < NUM_INV_SLOTS );
5573 		// and it better have a valid merc who owned it
5574 		Assert( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE );
5575 
5576 		// get soldier
5577 		SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(pInv->ubIdOfMercWhoOwnsTheItem);
5578 		Assert(s != NULL);
5579 		Assert(CanMercInteractWithSelectedShopkeeper(s));
5580 
5581 		//Copy the object back into that merc's original inventory slot
5582 		s->inv[pInv->bSlotIdInOtherLocation] = pInv->ItemObject;
5583 	}
5584 }
5585 
5586 
5587 static void IfMercOwnedRemoveItemFromMercInv2(UINT8 ubOwnerProfileId, INT8 bOwnerSlotId);
5588 
5589 
IfMercOwnedRemoveItemFromMercInv(const INVENTORY_IN_SLOT * pInv)5590 static void IfMercOwnedRemoveItemFromMercInv(const INVENTORY_IN_SLOT* pInv)
5591 {
5592 	IfMercOwnedRemoveItemFromMercInv2( pInv->ubIdOfMercWhoOwnsTheItem, pInv->bSlotIdInOtherLocation );
5593 }
5594 
5595 
IfMercOwnedRemoveItemFromMercInv2(UINT8 ubOwnerProfileId,INT8 bOwnerSlotId)5596 static void IfMercOwnedRemoveItemFromMercInv2(UINT8 ubOwnerProfileId, INT8 bOwnerSlotId)
5597 {
5598 	BOOLEAN fSuccess;
5599 	OBJECTTYPE ObjectToRemove;
5600 
5601 	//if this item was in a previous location, and that location is on a merc's inventory
5602 	if ( ( bOwnerSlotId != -1 ) && ( ubOwnerProfileId != NO_PROFILE ) )
5603 	{
5604 		// then it better be a valid slot #
5605 		Assert( bOwnerSlotId < NUM_INV_SLOTS );
5606 		// and it better have a valid merc who owned it
5607 		Assert( ubOwnerProfileId != NO_PROFILE );
5608 
5609 		SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(ubOwnerProfileId);
5610 		Assert(s != NULL);
5611 		Assert(CanMercInteractWithSelectedShopkeeper(s));
5612 
5613 		//remove the object from that merc's original inventory slot
5614 		fSuccess = RemoveObjectFromSlot(s, bOwnerSlotId, &ObjectToRemove);
5615 		Assert(fSuccess);
5616 	}
5617 }
5618 
5619 
5620 static BOOLEAN SKITryToAddInvToMercsInventory(INVENTORY_IN_SLOT* pInv, SOLDIERTYPE* pSoldier);
5621 
5622 
SKITryToReturnInvToOwnerOrCurrentMerc(INVENTORY_IN_SLOT * pInv)5623 static BOOLEAN SKITryToReturnInvToOwnerOrCurrentMerc(INVENTORY_IN_SLOT* pInv)
5624 {
5625 	// don't use this if the item has a copy in merc's inventory!!  It would create a duplicate!!
5626 	Assert( pInv->bSlotIdInOtherLocation == -1 );
5627 
5628 	// if it does have an owner
5629 	if( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE )
5630 	{
5631 		SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(pInv->ubIdOfMercWhoOwnsTheItem);
5632 		// if that soldier is not in player's hire any longer
5633 		if (s == NULL) return FALSE;
5634 
5635 		// For owners of repaired items, this checks that owner is still hired, in sector,
5636 		// on current squad, close enough to the shopkeeper, etc.
5637 		if (!CanMercInteractWithSelectedShopkeeper(s)) return FALSE;
5638 
5639 		// Try to find a place to put in its owner's inventory (regardless of which merc is currently displayed!)
5640 		if (SKITryToAddInvToMercsInventory(pInv, s)) return TRUE;
5641 
5642 		// owner's inventory is full, so we'll try to give it to the current merc instead
5643 	}
5644 
5645 
5646 	//if the current merc is not disabled
5647 	if( !gfSMDisableForItems )
5648 	{
5649 		// Try to find a place to put in current merc's inventory
5650 		if ( SKITryToAddInvToMercsInventory( pInv, gpSMCurrentMerc ) )
5651 		{
5652 			return( TRUE );
5653 		}
5654 	}
5655 
5656 	//failed to add item, either inventory was filled up, or nobody was even eligible to receive it
5657 	return( FALSE );
5658 }
5659 
5660 
SKITryToAddInvToMercsInventory(INVENTORY_IN_SLOT * pInv,SOLDIERTYPE * pSoldier)5661 static BOOLEAN SKITryToAddInvToMercsInventory(INVENTORY_IN_SLOT* pInv, SOLDIERTYPE* pSoldier)
5662 {
5663 	INT8    bMoneyInvPos;
5664 	BOOLEAN fNewItem = FALSE;
5665 
5666 
5667 	//if the item is money
5668 	if( GCM->getItem(pInv->sItemIndex)->getItemClass() == IC_MONEY )
5669 	{
5670 		// search through the merc's inventory for a pocket of money which isn't full already
5671 		bMoneyInvPos = GetInvSlotOfUnfullMoneyInMercInventory( pSoldier );
5672 
5673 		// if he has a money pocket going
5674 		if ( bMoneyInvPos != -1 )
5675 		{
5676 			// try to add to it.  If successful, it will delete the object...  It returns TRUE even if not all would fit!
5677 			PlaceObject( pSoldier, bMoneyInvPos, &( pInv->ItemObject ) );
5678 
5679 			// if the money is all gone
5680 			if ( pInv->ItemObject.uiMoneyAmount == 0 )
5681 			{
5682 				// we've been succesful!
5683 				return(TRUE);
5684 			}
5685 			// otherwise we'll try to place the rest seperately
5686 		}
5687 	}
5688 
5689 
5690 	// If it's just been purchased or repaired, mark it as a "new item"
5691 	fNewItem = ( BOOLEAN ) ( pInv->uiFlags & ( ARMS_INV_JUST_PURCHASED | ARMS_INV_ITEM_REPAIRED ) );
5692 
5693 	//try autoplacing the item in this soldier's inventory.
5694 	if( !ShopkeeperAutoPlaceObject( pSoldier, &pInv->ItemObject, fNewItem ) )
5695 	{
5696 		//failed to add item, inventory probably filled up
5697 		return( FALSE );
5698 	}
5699 
5700 	return(TRUE);
5701 }
5702 
5703 
CanMercInteractWithSelectedShopkeeper(const SOLDIERTYPE * pSoldier)5704 BOOLEAN CanMercInteractWithSelectedShopkeeper(const SOLDIERTYPE* pSoldier)
5705 {
5706 	INT16  sDestGridNo;
5707 	INT8   bDestLevel;
5708 	INT16  sDistVisible;
5709 	UINT32 uiRange;
5710 
5711 
5712 	Assert( pSoldier!= NULL );
5713 	Assert( gbSelectedArmsDealerID != -1 );
5714 
5715 	const SOLDIERTYPE* const pShopkeeper = FindSoldierByProfileID(SelectedArmsDealer()->profileID);
5716 	Assert( pShopkeeper != NULL );
5717 	Assert( pShopkeeper->bInSector );
5718 
5719 	if ( pShopkeeper->bLife < OKLIFE )
5720 	{
5721 		return( FALSE );
5722 	}
5723 
5724 	if ( pSoldier->bActive && pSoldier->bInSector && IsMercOnCurrentSquad( pSoldier ) && ( pSoldier->bLife >= OKLIFE ) &&
5725 		!IsMechanical(*pSoldier))
5726 	{
5727 		sDestGridNo = pShopkeeper->sGridNo;
5728 		bDestLevel = pShopkeeper->bLevel;
5729 
5730 		// is he close enough to see that gridno if he turns his head?
5731 		sDistVisible = DistanceVisible( pSoldier, DIRECTION_IRRELEVANT, DIRECTION_IRRELEVANT, sDestGridNo, bDestLevel );
5732 
5733 		// If he has LOS...
5734 		if ( SoldierTo3DLocationLineOfSightTest( pSoldier, sDestGridNo, bDestLevel, 3, (UINT8) sDistVisible, TRUE ) )
5735 		{
5736 			// Get range to shopkeeper
5737 			uiRange = GetRangeFromGridNoDiff( pSoldier->sGridNo, sDestGridNo );
5738 
5739 			// and is close enough to talk to the shopkeeper (use this define INSTEAD of PASSING_ITEM_DISTANCE_OKLIFE!)
5740 			if ( uiRange <= NPC_TALK_RADIUS )
5741 			{
5742 				return( TRUE );
5743 			}
5744 		}
5745 	}
5746 
5747 	return( FALSE );
5748 }
5749 
ExitSKIRequested(void)5750 static void ExitSKIRequested(void)
5751 {
5752 	BOOLEAN fPlayerOwnedStuffOnTable = FALSE;
5753 
5754 
5755 	ShutUpShopKeeper();
5756 
5757 	if( !gfRemindedPlayerToPickUpHisStuff )
5758 	{
5759 		if( AreThereItemsInThePlayersOfferArea( ) )
5760 		{
5761 			fPlayerOwnedStuffOnTable = TRUE;
5762 		}
5763 
5764 		if( (DoesDealerDoRepairs(gbSelectedArmsDealerID)) &&
5765 				AreThereItemsInTheArmsDealersOfferArea( ) )
5766 		{
5767 			fPlayerOwnedStuffOnTable = TRUE;
5768 		}
5769 
5770 		//if there are any items belonging to player still on the table
5771 		if( fPlayerOwnedStuffOnTable )
5772 		{
5773 			// remind player to pick them up
5774 			ShutUpShopKeeper( );
5775 			StartShopKeeperTalking( SK_QUOTES_PRESSED_DONE_STILL_HAS_STUFF_IN_OFFER_AREA );
5776 			gfRemindedPlayerToPickUpHisStuff = TRUE;
5777 			return;
5778 		}
5779 		else
5780 		{
5781 			//if the player has already requested to leave
5782 			if( gfUserHasRequestedToLeave )
5783 			{
5784 				// has already asked to leave, shut the dealer up and allow the player to leave
5785 				ShutUpShopKeeper();
5786 				return;
5787 			}
5788 		}
5789 	}
5790 
5791 
5792 	// if the player hasn't already requested to leave
5793 	if( !gfUserHasRequestedToLeave )
5794 	{
5795 		UINT16 usQuoteNum;
5796 
5797 		if( gfDoneBusinessThisSession )
5798 			usQuoteNum = SK_QUOTES_PRESSES_DONE_HAS_AT_LEAST_1_TRANSACTION;
5799 		else
5800 			usQuoteNum = SK_QUOTES_PRESSED_DONE_HASNT_MADE_TRANSACTION;
5801 
5802 		StartShopKeeperTalking( usQuoteNum );
5803 
5804 		//Set the fact that the player wants to leave
5805 		gfUserHasRequestedToLeave = TRUE;
5806 	}
5807 }
5808 
5809 
ResetAllQuoteSaidFlags()5810 static void ResetAllQuoteSaidFlags()
5811 {
5812 	// Reset flags for quotes said.
5813 	FOR_EACH(BOOLEAN, i, gfEvalResultQuoteSaid) *i = FALSE;
5814 
5815 	guiLastTimeDealerSaidNormalEvaluationQuote = 0;
5816 }
5817 
5818 
5819 static void ReturnItemToPlayerSomehow(INVENTORY_IN_SLOT* pInvSlot, SOLDIERTYPE* pDropSoldier);
5820 
5821 
DealWithItemsStillOnTheTable(void)5822 static void DealWithItemsStillOnTheTable(void)
5823 {
5824 	UINT8 ubCnt;
5825 	SOLDIERTYPE *pDropSoldier;
5826 
5827 
5828 	// in case we have have to drop stuff off at someone's feet, figure out where it's all gonna go
5829 
5830 	// use the current merc, unless he's ineligible, then use the selected merc instead.
5831 	if( !gfSMDisableForItems )
5832 	{
5833 		pDropSoldier = gpSMCurrentMerc;
5834 	}
5835 	else
5836 	{
5837 		pDropSoldier = GetSelectedMan();
5838 	}
5839 
5840 	// this guy HAS to be valid!
5841 	Assert( CanMercInteractWithSelectedShopkeeper( pDropSoldier ) );
5842 
5843 
5844 	//loop through the players offer area and add return any items there to the player
5845 	for( ubCnt=0; ubCnt<SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
5846 	{
5847 		INVENTORY_IN_SLOT* const o = &PlayersOfferArea[ubCnt];
5848 		//if there is an item here, give it back somehow
5849 		if (o->fActive)
5850 		{
5851 			ReturnItemToPlayerSomehow(o, pDropSoldier);
5852 			ClearPlayersOfferSlot( ubCnt );
5853 		}
5854 	}
5855 
5856 
5857 	//if the dealer repairs
5858 	if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
5859 	{
5860 		//loop through the arms dealers' offer area and return any items there to the player
5861 		for( ubCnt = 0; ubCnt < SKI_NUM_TRADING_INV_SLOTS; ubCnt++)
5862 		{
5863 			INVENTORY_IN_SLOT* const a = &ArmsDealerOfferArea[ubCnt];
5864 		//if there is an item here, give it back somehow
5865 			if (a->fActive)
5866 			{
5867 				ReturnItemToPlayerSomehow(a, pDropSoldier);
5868 				ClearArmsDealerOfferSlot( ubCnt );
5869 			}
5870 		}
5871 	}
5872 }
5873 
5874 
ReturnItemToPlayerSomehow(INVENTORY_IN_SLOT * pInvSlot,SOLDIERTYPE * pDropSoldier)5875 static void ReturnItemToPlayerSomehow(INVENTORY_IN_SLOT* pInvSlot, SOLDIERTYPE* pDropSoldier)
5876 {
5877 	//if the item doesn't have a duplicate copy in its owner merc's inventory slot
5878 	if( pInvSlot->bSlotIdInOtherLocation == -1 )
5879 	{
5880 		// first try to give it to its owner, or if he's unavailable, the current merc
5881 		if ( !SKITryToReturnInvToOwnerOrCurrentMerc( pInvSlot ) )
5882 		{
5883 			// failed to add item, inventory probably filled up or item is unowned and current merc ineligible.
5884 			// drop it at the specified guy's feet instead
5885 			ShopkeeperAddItemToPool(pDropSoldier->sGridNo, &pInvSlot->ItemObject, pDropSoldier->bLevel);
5886 		}
5887 	}
5888 }
5889 
5890 
GivePlayerSomeChange(UINT32 uiAmount)5891 static void GivePlayerSomeChange(UINT32 uiAmount)
5892 {
5893 	INVENTORY_IN_SLOT MoneyInvSlot;
5894 
5895 	MoneyInvSlot = INVENTORY_IN_SLOT{};
5896 
5897 	CreateMoney( uiAmount, &MoneyInvSlot.ItemObject );
5898 	MoneyInvSlot.sItemIndex = MoneyInvSlot.ItemObject.usItem;
5899 	MoneyInvSlot.ubLocationOfObject = PLAYERS_INVENTORY;
5900 	MoneyInvSlot.ubIdOfMercWhoOwnsTheItem = NO_PROFILE;
5901 
5902 	AddItemToPlayersOfferArea( NO_PROFILE, &MoneyInvSlot, -1 );
5903 
5904 	if ( ( gbSelectedArmsDealerID == ARMS_DEALER_JAKE ) &&
5905 		( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash < uiAmount ) )
5906 	{
5907 		// HACK HACK HACK: We forgot to write/record quote 27 for Jake, so he ALWAYS must have enough money!
5908 		gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash += ( uiAmount - gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash );
5909 	}
5910 
5911 	// he must have enough cash left to give change
5912 	Assert( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash >= uiAmount );
5913 	// subtract change from dealer's cash
5914 	gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash -= uiAmount;
5915 }
5916 
5917 
DealerGetsBribed(UINT8 ubProfileId,UINT32 uiMoneyAmount)5918 static void DealerGetsBribed(UINT8 ubProfileId, UINT32 uiMoneyAmount)
5919 {
5920 	BOOLEAN fBribable = FALSE;
5921 	UINT32 uiMinBribe = 0;
5922 
5923 	// this is only for dealers
5924 	if (!IsMercADealer( ubProfileId ) )
5925 	{
5926 		return;
5927 	}
5928 
5929 	Fact usFact = FACT_NONE;
5930 	switch ( ubProfileId )
5931 	{
5932 		case FRANK:
5933 			fBribable = TRUE;
5934 			uiMinBribe = 10; // This should match the handling of a bribe submitted outside the SKI, using the GIVE system
5935 			usFact = FACT_FRANK_HAS_BEEN_BRIBED;
5936 			break;
5937 	}
5938 
5939 	// if this guy can be bribed, and we gave enough to bribe him
5940 	if ( fBribable && ( uiMoneyAmount >= uiMinBribe ) )
5941 	{
5942 		// set the appropriate fact
5943 		SetFactTrue( usFact );
5944 	}
5945 }
5946 
5947 
5948 static void DelayRepairsInProgressBy(UINT32 uiMinutesDelayed);
5949 static BOOLEAN RepairmanFixingAnyItemsThatShouldBeDoneNow(UINT32* puiHoursSinceOldestItemRepaired);
5950 
5951 
HandlePossibleRepairDelays(void)5952 static void HandlePossibleRepairDelays(void)
5953 {
5954 	// assume there won't be a delay
5955 	gfStartWithRepairsDelayedQuote = FALSE;
5956 
5957 	if (!GetDealer(gbSelectedArmsDealerID)->hasFlag(ArmsDealerFlag::DELAYS_REPAIR)) return;
5958 
5959 	ARMS_DEALER_STATUS& status = gArmsDealerStatus[gbSelectedArmsDealerID];
5960 	// because the quotes are so specific, we'll only use them once per game per repairman
5961 	if (status.fRepairDelayBeenUsed) return;
5962 
5963 	// and it's been a while since the player last dealt with this repairman (within SKI that is)
5964 	// this serves 2 purposes:
5965 	// a) reduces delays being much more likely if player checks time remaining very frequently, AND
5966 	// b) gives time for the events described in the text of the dealers' excuses to happen (e.g. scouting trip)
5967 	if (GetWorldTotalMin() - status.uiTimePlayerLastInSKI < 3 * 60) return;
5968 
5969 	// if he should have been finished, but it's only been a few hours since then (not days!)
5970 	UINT32 uiHoursSinceAnyItemsShouldHaveBeenRepaired = 0;
5971 	if (!RepairmanFixingAnyItemsThatShouldBeDoneNow(&uiHoursSinceAnyItemsShouldHaveBeenRepaired)) return;
5972 	if (uiHoursSinceAnyItemsShouldHaveBeenRepaired >= REPAIR_DELAY_IN_HOURS) return;
5973 
5974 	// then there's a fair chance he'll be delayed.  Use pre-chance to hopefully preserve across reloads
5975 	if (!PreChance(50)) return;
5976 
5977 	DelayRepairsInProgressBy((REPAIR_DELAY_IN_HOURS + Random(REPAIR_DELAY_IN_HOURS)) * 60);
5978 
5979 	// this triggers a different opening quote
5980 	gfStartWithRepairsDelayedQuote = TRUE;
5981 
5982 	// set flag so it doesn't happen again
5983 	status.fRepairDelayBeenUsed = TRUE;
5984 }
5985 
5986 
RepairmanFixingAnyItemsThatShouldBeDoneNow(UINT32 * puiHoursSinceOldestItemRepaired)5987 static BOOLEAN RepairmanFixingAnyItemsThatShouldBeDoneNow(UINT32* puiHoursSinceOldestItemRepaired)
5988 {
5989 	UINT16 usItemIndex;
5990 	UINT8  ubElement;
5991 	DEALER_ITEM_HEADER *pDealerItem;
5992 	DEALER_SPECIAL_ITEM *pSpecialItem;
5993 	BOOLEAN fFoundOne = FALSE;
5994 	UINT32 uiMinutesSinceItWasDone;
5995 	UINT32 uiMinutesShopClosedSinceItWasDone;
5996 	UINT32 uiWorkingHoursSinceThisItemRepaired;
5997 
5998 
5999 	//if the dealer is not a repair dealer, return
6000 	if( !DoesDealerDoRepairs( gbSelectedArmsDealerID ) )
6001 		return( FALSE );
6002 
6003 	*puiHoursSinceOldestItemRepaired = 0;
6004 
6005 	//loop through the dealers inventory and check if there are only unrepaired items
6006 	for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
6007 	{
6008 		pDealerItem = &( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ] );
6009 
6010 		//if there is some items in stock
6011 		if( pDealerItem->ubTotalItems )
6012 		{
6013 			//loop through the array of items
6014 			Assert(pDealerItem->SpecialItem.size() <= UINT8_MAX);
6015 			for (ubElement = 0; ubElement < static_cast<UINT8>(pDealerItem->SpecialItem.size()); ubElement++)
6016 			{
6017 				pSpecialItem = &( pDealerItem->SpecialItem[ ubElement ] );
6018 
6019 				if ( pSpecialItem->fActive )
6020 				{
6021 					//if the items status is below 0, the item is being repaired
6022 					if( pSpecialItem->Info.bItemCondition < 0 )
6023 					{
6024 						//if the repairs are done
6025 						if( pSpecialItem->uiRepairDoneTime <= GetWorldTotalMin() )
6026 						{
6027 							// at least one item is supposed to be done by now
6028 							fFoundOne = TRUE;
6029 
6030 							uiMinutesSinceItWasDone = GetWorldTotalMin() - pSpecialItem->uiRepairDoneTime;
6031 							uiMinutesShopClosedSinceItWasDone = CalculateMinutesClosedBetween( gbSelectedArmsDealerID, pSpecialItem->uiRepairDoneTime, GetWorldTotalMin() );
6032 
6033 							// calculate how many WORKING hours since this item's been repaired
6034 							uiWorkingHoursSinceThisItemRepaired = ( uiMinutesSinceItWasDone - uiMinutesShopClosedSinceItWasDone ) / 60;
6035 
6036 							// we need to determine how long it's been since the item that's been repaired for the longest time was done
6037 							if ( uiWorkingHoursSinceThisItemRepaired > *puiHoursSinceOldestItemRepaired )
6038 							{
6039 								*puiHoursSinceOldestItemRepaired = uiWorkingHoursSinceThisItemRepaired;
6040 							}
6041 						}
6042 					}
6043 				}
6044 			}
6045 		}
6046 	}
6047 
6048 
6049 	// if FALSE returned here, he's either not repairing anything, or none of it's done yet
6050 	return( fFoundOne );
6051 }
6052 
6053 
DelayRepairsInProgressBy(UINT32 uiMinutesDelayed)6054 static void DelayRepairsInProgressBy(UINT32 uiMinutesDelayed)
6055 {
6056 	UINT16 usItemIndex;
6057 	UINT8  ubElement;
6058 	DEALER_ITEM_HEADER *pDealerItem;
6059 	DEALER_SPECIAL_ITEM *pSpecialItem;
6060 	UINT32 uiMinutesShopClosedBeforeItsDone;
6061 
6062 
6063 	//if the dealer is not a repair dealer, return
6064 	if( !DoesDealerDoRepairs( gbSelectedArmsDealerID ) )
6065 		return;
6066 
6067 	//loop through the dealers inventory and check if there are only unrepaired items
6068 	for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
6069 	{
6070 		pDealerItem = &( gArmsDealersInventory[ gbSelectedArmsDealerID ][ usItemIndex ] );
6071 
6072 		//if there is some items in stock
6073 		if( pDealerItem->ubTotalItems )
6074 		{
6075 			//loop through the array of items
6076 			Assert(pDealerItem->SpecialItem.size() <= UINT8_MAX);
6077 			for (ubElement = 0; ubElement < static_cast<UINT8>(pDealerItem->SpecialItem.size()); ubElement++)
6078 			{
6079 				pSpecialItem = &( pDealerItem->SpecialItem[ ubElement ] );
6080 
6081 				if ( pSpecialItem->fActive )
6082 				{
6083 					//if the items status is below 0, the item is being repaired
6084 					if( pSpecialItem->Info.bItemCondition < 0 )
6085 					{
6086 						uiMinutesShopClosedBeforeItsDone = CalculateOvernightRepairDelay( gbSelectedArmsDealerID, pSpecialItem->uiRepairDoneTime, uiMinutesDelayed );
6087 
6088 						// add this many minutes to the repair time estimate
6089 						pSpecialItem->uiRepairDoneTime += ( uiMinutesShopClosedBeforeItsDone + uiMinutesDelayed );
6090 					}
6091 				}
6092 			}
6093 		}
6094 	}
6095 }
6096 
6097 
6098 //Mouse Call back for the Arms delaers face
SelectArmsDealersDropItemToGroundRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)6099 static void SelectArmsDealersDropItemToGroundRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
6100 {
6101 	if (iReason & MSYS_CALLBACK_REASON_LBUTTON_UP)
6102 	{
6103 		SOLDIERTYPE *pDropSoldier;
6104 
6105 		// use the current merc, unless he's ineligible, then use the selected merc instead.
6106 		if( !gfSMDisableForItems )
6107 		{
6108 			pDropSoldier = gpSMCurrentMerc;
6109 		}
6110 		else
6111 		{
6112 			pDropSoldier = GetSelectedMan();
6113 		}
6114 
6115 		//if we don't have an item, pick one up
6116 		if( gMoveingItem.sItemIndex != 0 )
6117 		{
6118 			//add the item to the ground
6119 			ShopkeeperAddItemToPool(pDropSoldier->sGridNo, &gMoveingItem.ItemObject, pDropSoldier->bLevel);
6120 
6121 			//Reset the cursor
6122 			SetSkiCursor( CURSOR_NORMAL );
6123 
6124 			//refresh the screen
6125 			gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
6126 		}
6127 	}
6128 }
6129 
6130 
6131 static BOOLEAN CanTheDropItemToGroundStringBeDisplayed(void);
6132 
6133 
SelectArmsDealersDropItemToGroundMovementRegionCallBack(MOUSE_REGION * pRegion,INT32 iReason)6134 static void SelectArmsDealersDropItemToGroundMovementRegionCallBack(MOUSE_REGION* pRegion, INT32 iReason)
6135 {
6136 	if( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE )
6137 	{
6138 		gfSkiDisplayDropItemToGroundText = FALSE;
6139 		gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
6140 	}
6141 	else if( iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE )
6142 	{
6143 		if( CanTheDropItemToGroundStringBeDisplayed() )
6144 		{
6145 			gfSkiDisplayDropItemToGroundText = TRUE;
6146 		}
6147 		else
6148 		{
6149 			gubSkiDirtyLevel = SKI_DIRTY_LEVEL2;
6150 			gfSkiDisplayDropItemToGroundText = FALSE;
6151 		}
6152 	}
6153 }
6154 
6155 
CanTheDropItemToGroundStringBeDisplayed(void)6156 static BOOLEAN CanTheDropItemToGroundStringBeDisplayed(void)
6157 {
6158 	//if we don't have an item, pick one up
6159 	if( gMoveingItem.sItemIndex != 0 )
6160 		return( TRUE );
6161 	else
6162 		return( FALSE );
6163 }
6164 
6165 
DisplayTheSkiDropItemToGroundString(void)6166 static void DisplayTheSkiDropItemToGroundString(void)
6167 {
6168 	UINT16 usHeight;
6169 
6170 	//get the height of the displayed text
6171 	usHeight = DisplayWrappedString(SKI_DROP_ITEM_TO_GROUND_START_X, SKI_DROP_ITEM_TO_GROUND_TEXT_START_Y, SCREEN_WIDTH - SKI_DROP_ITEM_TO_GROUND_START_X, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_DROP_ITEM_TO_GROUND], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED | DONT_DISPLAY_TEXT);
6172 
6173 	//display the 'drop item to ground' text
6174 	DisplayWrappedString(SKI_DROP_ITEM_TO_GROUND_START_X, SKI_DROP_ITEM_TO_GROUND_TEXT_START_Y - usHeight, SCREEN_WIDTH - SKI_DROP_ITEM_TO_GROUND_START_X, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_DROP_ITEM_TO_GROUND], FONT_MCOLOR_BLACK, CENTER_JUSTIFIED | INVALIDATE_TEXT);
6175 }
6176 
6177 
EvaluateInvSlot(INVENTORY_IN_SLOT * pInvSlot)6178 static UINT32 EvaluateInvSlot(INVENTORY_IN_SLOT* pInvSlot)
6179 {
6180 	UINT32 uiEvalResult = EVAL_RESULT_NORMAL;
6181 	FLOAT  dPriceModifier;
6182 	UINT32 uiBuyingPrice;
6183 
6184 
6185 	//if the dealer is Micky
6186 	if( gbSelectedArmsDealerID == ARMS_DEALER_MICKY )
6187 	{
6188 		const SOLDIERTYPE* const s = FindSoldierByProfileIDOnPlayerTeam(SelectedArmsDealer()->profileID);
6189 		if (s != NULL && GetDrunkLevel(s) == DRUNK)
6190 		{
6191 			//Micky is DRUNK, pays more!
6192 			dPriceModifier = SelectedArmsDealer()->sellingPrice;
6193 		}
6194 		else
6195 		{
6196 			// Micky isn't drunk, charge regular price
6197 			dPriceModifier = SelectedArmsDealer()->buyingPrice;
6198 		}
6199 	}
6200 	else
6201 	{
6202 		dPriceModifier = SelectedArmsDealer()->buyingPrice;
6203 	}
6204 
6205 
6206 	// Calculate dealer's buying price for the item
6207 	uiBuyingPrice = CalcShopKeeperItemPrice( DEALER_BUYING, FALSE, pInvSlot->sItemIndex, dPriceModifier, &( pInvSlot->ItemObject ) );
6208 
6209 	pInvSlot->uiItemPrice = uiBuyingPrice;
6210 
6211 	if ( uiBuyingPrice > 0 )
6212 	{
6213 		// check if the item is really badly damaged
6214 		if( GCM->getItem(pInvSlot->sItemIndex)->getItemClass() != IC_AMMO )
6215 		{
6216 			if( pInvSlot->ItemObject.bStatus[ 0 ] < REALLY_BADLY_DAMAGED_THRESHOLD )
6217 			{
6218 				uiEvalResult = EVAL_RESULT_OK_BUT_REALLY_DAMAGED;
6219 			}
6220 		}
6221 
6222 		//the item can be sold here, so mark it as such
6223 		pInvSlot->uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE;
6224 	}
6225 	else
6226 	{
6227 		// he normally buys this, but it's in such bad shape that he won't, it's worthless (different quote)
6228 		uiEvalResult = EVAL_RESULT_WORTHLESS;
6229 
6230 		pInvSlot->uiFlags &= ~ARMS_INV_PLAYERS_ITEM_HAS_VALUE;
6231 	}
6232 
6233 	return( uiEvalResult );
6234 }
6235 
6236 
6237 
6238 // round off reapir times shown to the near quarter-hour
6239 #define REPAIR_MINUTES_INTERVAL 15
6240 
6241 
BuildRepairTimeString(UINT32 uiTimeInMinutesToFixItem)6242 static ST::string BuildRepairTimeString(UINT32 uiTimeInMinutesToFixItem)
6243 {
6244 	UINT16 usNumberOfHoursToFixItem = 0;
6245 
6246 
6247 	// if it's 0, it shouldn't be up here any more!
6248 	Assert ( uiTimeInMinutesToFixItem > 0 );
6249 
6250 
6251 	// round off to next higher 15 minutes
6252 	if ( ( uiTimeInMinutesToFixItem % REPAIR_MINUTES_INTERVAL ) > 0 )
6253 	{
6254 		uiTimeInMinutesToFixItem += REPAIR_MINUTES_INTERVAL - ( uiTimeInMinutesToFixItem % REPAIR_MINUTES_INTERVAL );
6255 	}
6256 
6257 	if ( uiTimeInMinutesToFixItem < REPAIR_MINUTES_INTERVAL )
6258 	{
6259 		uiTimeInMinutesToFixItem = REPAIR_MINUTES_INTERVAL;
6260 	}
6261 
6262 	// show up to 1.5 hrs in minutes
6263 	if ( uiTimeInMinutesToFixItem <= 90 )
6264 	{
6265 		// show minutes
6266 		return st_format_printf(SKI_Text[ SKI_TEXT_MINUTES ], uiTimeInMinutesToFixItem);
6267 	}
6268 	else
6269 	{
6270 		// show hours
6271 		// round fractions of 15+ minutes up to next full hour
6272 		usNumberOfHoursToFixItem = (UINT16) ( ( uiTimeInMinutesToFixItem + 45 ) / 60 );
6273 		return st_format_printf(SKI_Text[ SKI_TEXT_PLURAL_HOURS ], usNumberOfHoursToFixItem);
6274 	}
6275 }
6276 
6277 
BuildDoneWhenTimeString(ArmsDealerID ubArmsDealer,UINT16 usItemIndex,UINT8 ubElement)6278 static ST::string BuildDoneWhenTimeString(ArmsDealerID ubArmsDealer, UINT16 usItemIndex, UINT8 ubElement)
6279 {
6280 	UINT32 uiDoneTime;
6281 	UINT32 uiDay, uiHour, uiMin;
6282 
6283 
6284 	//dealer must be a repair dealer
6285 	Assert( DoesDealerDoRepairs( ubArmsDealer ) );
6286 	// element index must be valid
6287 	Assert( ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem.size() );
6288 	// that item must be active
6289 	Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive );
6290 	// that item must be in repair
6291 	Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 );
6292 
6293 	//if the item has already been repaired
6294 	if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime <= GetWorldTotalMin() )
6295 	{
6296 		return ST::null;
6297 	}
6298 
6299 	uiDoneTime = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime;
6300 
6301 	// round off to next higher 15 minutes
6302 	if ( ( uiDoneTime % REPAIR_MINUTES_INTERVAL ) > 0 )
6303 	{
6304 		uiDoneTime += REPAIR_MINUTES_INTERVAL - ( uiDoneTime % REPAIR_MINUTES_INTERVAL );
6305 	}
6306 
6307 	// uiDoneTime is in minutes...
6308 	uiDay  = ( uiDoneTime / NUM_MIN_IN_DAY );
6309 	uiHour = ( uiDoneTime - ( uiDay * NUM_MIN_IN_DAY ) ) / NUM_MIN_IN_HOUR;
6310 	uiMin  = uiDoneTime - ( ( uiDay * NUM_MIN_IN_DAY ) + ( uiHour * NUM_MIN_IN_HOUR ) );
6311 
6312 	// only show day if it's gonna take overnight
6313 	if ( GetWorldDay() != uiDay )
6314 	{
6315 		return ST::format("{} {} {02d}:{02d}", pDayStrings, uiDay, uiHour, uiMin);
6316 	}
6317 	else
6318 	{
6319 		return ST::format("{02d}:{02d}", uiHour, uiMin);
6320 	}
6321 }
6322 
6323 
BuildItemHelpTextString(const INVENTORY_IN_SLOT * pInv,UINT8 ubScreenArea)6324 static ST::string BuildItemHelpTextString(const INVENTORY_IN_SLOT* pInv, UINT8 ubScreenArea)
6325 {
6326 	ST::string zHelpText;
6327 	ST::string zRepairTime;
6328 
6329 	if( pInv != NULL )
6330 	{
6331 		zHelpText = GetHelpTextForItem(pInv->ItemObject);
6332 
6333 		// add repair time for items in a repairman's offer area
6334 		if ( ( ubScreenArea == ARMS_DEALER_OFFER_AREA ) &&
6335 			(DoesDealerDoRepairs(gbSelectedArmsDealerID)) )
6336 		{
6337 			zRepairTime = BuildRepairTimeString(CalculateObjectItemRepairTime( gbSelectedArmsDealerID, &( pInv->ItemObject ) ));
6338 			return ST::format("{}\n({}: {})", zHelpText, gzLateLocalizedString[STR_LATE_44], zRepairTime);
6339 		}
6340 		else
6341 		{
6342 			return zHelpText;
6343 		}
6344 	}
6345 	else
6346 	{
6347 		return ST::null;
6348 	}
6349 }
6350 
6351 
DisableAllDealersInventorySlots(void)6352 static void DisableAllDealersInventorySlots(void)
6353 {
6354 	INT32 iCnt;
6355 
6356 	for (iCnt = 0; iCnt < SKI_NUM_ARMS_DEALERS_INV_SLOTS; iCnt++)
6357 	{
6358 		gDealersInventoryMouseRegions[iCnt].Disable();
6359 
6360 		//if the dealer repairs
6361 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
6362 		{
6363 			gRepairmanInventorySmallFaceMouseRegions[iCnt].Disable();
6364 		}
6365 	}
6366 }
6367 
6368 
EnableAllDealersInventorySlots(void)6369 static void EnableAllDealersInventorySlots(void)
6370 {
6371 	INT32 iCnt;
6372 
6373 	for (iCnt = 0; iCnt < SKI_NUM_ARMS_DEALERS_INV_SLOTS; iCnt++)
6374 	{
6375 		gDealersInventoryMouseRegions[iCnt].Enable();
6376 
6377 		//if the dealer repairs
6378 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
6379 		{
6380 			gRepairmanInventorySmallFaceMouseRegions[iCnt].Enable();
6381 		}
6382 	}
6383 }
6384 
6385 
DisableAllDealersOfferSlots(void)6386 static void DisableAllDealersOfferSlots(void)
6387 {
6388 	INT32 iCnt;
6389 
6390 	for (iCnt = 0; iCnt < SKI_NUM_TRADING_INV_SLOTS; iCnt++)
6391 	{
6392 		gDealersOfferSlotsMouseRegions[iCnt].Disable();
6393 
6394 		//if the dealer repairs
6395 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
6396 		{
6397 			gDealersOfferSlotsSmallFaceMouseRegions[iCnt].Disable();
6398 		}
6399 	}
6400 }
6401 
6402 
EnableAllDealersOfferSlots(void)6403 static void EnableAllDealersOfferSlots(void)
6404 {
6405 	INT32 iCnt;
6406 
6407 	for (iCnt = 0; iCnt < SKI_NUM_TRADING_INV_SLOTS; iCnt++)
6408 	{
6409 		gDealersOfferSlotsMouseRegions[iCnt].Enable();
6410 
6411 		//if the dealer repairs
6412 		if (DoesDealerDoRepairs(gbSelectedArmsDealerID))
6413 		{
6414 			gDealersOfferSlotsSmallFaceMouseRegions[iCnt].Enable();
6415 		}
6416 	}
6417 }
6418 
6419 
HatchOutInvSlot(UINT16 usPosX,UINT16 usPosY)6420 static void HatchOutInvSlot(UINT16 usPosX, UINT16 usPosY)
6421 {
6422 	//if we are in the item desc panel
6423 	if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL )
6424 	{
6425 		// do nothing, don't wanna shade things twice!
6426 		return;
6427 	}
6428 
6429 	const UINT16 usSlotWidth  = SKI_INV_SLOT_WIDTH;
6430 	const UINT16 usSlotHeight = SKI_INV_SLOT_HEIGHT;
6431 
6432 	//Hatch it out
6433 	DrawHatchOnInventory(FRAME_BUFFER, usPosX, usPosY, usSlotWidth, usSlotHeight);
6434 	InvalidateRegion(usPosX, usPosY, usPosX + usSlotWidth, usPosY + usSlotHeight);
6435 }
6436