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, ¢ering_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