#include "Types.h" #include "ShopKeeper Interface.h" #include "Utilities.h" #include "Game Clock.h" #include "Render Dirty.h" #include "Font Control.h" #include "WordWrap.h" #include "Text Input.h" #include "Interface.h" #include "Interface Control.h" #include "Overhead.h" #include "Cursors.h" #include "SysUtil.h" #include "Interface Panels.h" #include "Radar Screen.h" #include "Interface Items.h" #include "Interface Utils.h" #include "VObject_Blitters.h" #include "Finances.h" #include "Text.h" #include "Cursor Control.h" #include "Input.h" #include "Arms Dealer Init.h" #include "english.h" #include "Soldier Add.h" #include "Faces.h" #include "Dialogue Control.h" #include "ShopKeeper Quotes.h" #include "GameSettings.h" #include "MercTextBox.h" #include "Random.h" #include "Squads.h" #include "Soldier Profile.h" #include "Message.h" #include "Personnel.h" #include "LaptopSave.h" #include "Quests.h" #include "Weapons.h" #include "MessageBoxScreen.h" #include "LINE.H" #include "Drugs and Alcohol.h" #include "Map Screen Interface.h" #include "Soldier macros.h" #include "armsdealerinvinit.h" #include "opplist.h" #include "los.h" #include "NPC.h" #include "Soldier Create.h" #include "PATHAI.h" #include "Points.h" #include "InterfaceItemImages.h" #include "Encyclopedia_new.h" #include "Animation Control.h" // added by Flugente #include "Town Militia.h" // added by Flugente #ifdef JA2UB #include "Explosion Control.h" #include "Ja25_Tactical.h" #include "Ja25 Strategic Ai.h" #include "MapScreen Quotes.h" #include "email.h" #include "interface Dialogue.h" #include "Ja25_Tactical.h" #include "ub_config.h" #endif #include "BuildDefines.h" #include //////////////////////////////////////////// // // global defines // /////////////////////////////////////////// #define SKI_INTERFACE_WIDTH 536 #define SKI_INTERFACE_HEIGHT 340 #define SCREEN_X_OFFSET (((SCREEN_WIDTH - SKI_INTERFACE_WIDTH) / 2)) #define SCREEN_Y_OFFSET ((((SCREEN_HEIGHT - INV_INTERFACE_HEIGHT) - (SKI_INTERFACE_HEIGHT)) / 2)) #define SKI_BUTTON_FONT MILITARYFONT1//FONT14ARIAL #define SKI_BUTTON_COLOR 73 #define SKI_TITLE_FONT MILITARYFONT1//FONT14ARIAL #define SKI_TITLE_COLOR 169//FONT_MCOLOR_LTYELLOW #define SKI_LABEL_FONT MILITARYFONT1 #define SKI_ITEM_DESC_FONT SMALLCOMPFONT #define SKI_ITEM_PRICE_COLOR FONT_MCOLOR_WHITE #define SKIT_NUMBER_FONT BLOCKFONT2 //Number of Inventory slots #define SKI_NUM_ARMS_DEALERS_INV_SLOTS 500 // Flugente: simply a resonably huge number #define SKI_NUM_TRADING_INV_SLOTS 500 // Flugente: simply a resonably huge number //Inventory Slots size and offsets #define SKI_INV_SLOT_WIDTH 67 #define SKI_INV_SLOT_HEIGHT 35 // bfant: increased from 31 to fit item name text #define SKI_INV_HEIGHT (SKI_INV_SLOT_HEIGHT - 7) #define SKI_INV_WIDTH 60 #define SKI_INV_OFFSET_X 74 #define SKI_INV_OFFSET_Y 40 // bfant: increased from 36 #define SKI_INV_PRICE_OFFSET_X 1 #define SKI_INV_PRICE_OFFSET_Y 24 // the delay for glow cycling in glow areas, in millisecs #define SKI_GLOW_DELAY 70 #define SKI_TACTICAL_BACKGROUND_START_X (SCREEN_X_OFFSET + SKI_INTERFACE_WIDTH) #define SKI_TACTICAL_BACKGROUND_START_Y 0 #define SKI_DROP_ITEM_TO_GROUND_START_X SKI_TACTICAL_BACKGROUND_START_X #define SKI_DROP_ITEM_TO_GROUND_START_Y (SCREEN_Y_OFFSET + 262) #define SKI_DROP_ITEM_TO_GROUND_TEXT_START_Y (SCREEN_Y_OFFSET + 262) #define SKI_ITEM_MOVEMENT_AREA_X (SCREEN_X_OFFSET + 85) #define SKI_ITEM_MOVEMENT_AREA_Y (SCREEN_Y_OFFSET + 263) #define SKI_ITEM_MOVEMENT_AREA_WIDTH (SCREEN_WIDTH - SKI_ITEM_MOVEMENT_AREA_X) #define SKI_ITEM_MOVEMENT_AREA_HEIGHT (SCREEN_Y_OFFSET + 215)//72 #define SKI_DEALER_OFFER_AREA_Y (SCREEN_Y_OFFSET + 148) #define SKI_ITEM_NUMBER_TEXT_OFFSET_X 50 #define SKI_ITEM_NUMBER_TEXT_OFFSET_Y 19 // bfant: increased from 15 to fit item name text #define SKI_ITEM_NUMBER_TEXT_WIDTH 15 #define SKI_SUBTITLE_TEXT_SIZE 512 #define SKI_POSITION_SUBTITLES_Y (SCREEN_Y_OFFSET + INV_INTERFACE_HEIGHT)//100 #define SKI_SMALL_FACE_WIDTH 16 #define SKI_SMALL_FACE_HEIGHT 14 #define SKI_SMALL_FACE_OFFSET_X 52 #define SKI_SMALL_FACE_OFFSET_Y 17 #define SKI_ATTACHMENT_SYMBOL_X_OFFSET 56 #define SKI_ATTACHMENT_SYMBOL_Y_OFFSET 14 #define SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME 4 // round off reapir times shown to the near quarter-hour #define REPAIR_MINUTES_INTERVAL 15 #define SKI_DEALERS_RANDOM_QUOTE_DELAY 15000 #define SKI_DEALERS_RANDOM_QUOTE_DELAY_INCREASE_RATE 5000 #define DELAY_FOR_SHOPKEEPER_IDLE_QUOTE 20000 #define CHANCE_FOR_SHOPKEEPER_IDLE_QUOTE 40 #define REALLY_BADLY_DAMAGED_THRESHOLD 30 #define REPAIR_DELAY_IN_HOURS 6 #define FLO_DISCOUNT_PERCENTAGE 10 #ifdef JA2UB #define SKI_BETTY_MINIMUM_AMOUNT_TO_SET_FACT 10 #define SKI_RAUL_MINIMUM_AMOUNT_TO_SET_FACT 100 #endif // Flugente: new defines for shopkeeper interface with a resolution-dependent size // the main tradescreen is a rectangle, which contains trader inventory, trader offer area and player offer area as sub-rectangles #define SKI_TOTAL_END_X (SCREEN_X_OFFSET + SKI_INTERFACE_WIDTH) #define SKI_TOTAL_END_Y (SCREEN_HEIGHT - INV_INTERFACE_HEIGHT) #define SKI_TOTAL_BEGIN_Y 0 // we determine how many item boxes we could display in a row, and then depend where the shopkeeper display should start // as a result, the margins will always look the same #define SKI_TOTAL_BEGIN_X (SKI_TOTAL_END_X - 166 - ((SKI_TOTAL_END_X - 166) / SKI_INV_OFFSET_X) * SKI_INV_OFFSET_X) // first: trader inventory #define SKI_TRADER_INVENTORY_BEGIN_X SKI_TOTAL_BEGIN_X #define SKI_TRADER_INVENTORY_BEGIN_Y SKI_TOTAL_BEGIN_Y #define SKI_TRADER_INVENTORY_END_X SKI_TOTAL_END_X #define SKI_TRADER_INVENTORY_END_Y (143 + (SKI_TOTAL_END_Y - SKI_INTERFACE_HEIGHT) / 2) #define SKI_MAIN_TITLE_X (SKI_TRADER_INVENTORY_BEGIN_X + 112) #define SKI_MAIN_TITLE_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 12) #define SKI_MAIN_TITLE_WIDTH 420 // merchant face #define SKI_FACE_X (SKI_TRADER_INVENTORY_BEGIN_X + 13) #define SKI_FACE_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 13) #define SKI_FACE_WIDTH 90 #define SKI_FACE_HEIGHT 100 // trader inventory page buttons #define SKI_PAGE_UP_ARROWS_X (SKI_TRADER_INVENTORY_BEGIN_X + 121) #define SKI_PAGE_UP_ARROWS_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 35) #define SKI_PAGE_DOWN_ARROWS_X SKI_PAGE_UP_ARROWS_X #define SKI_PAGE_DOWN_ARROWS_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 102) #define SKI_PAGE_X (SKI_TRADER_INVENTORY_BEGIN_X + 112) #define SKI_PAGE_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 70) #define SKI_PAGE_WIDTH 45 #define SKI_PAGE_HEIGHT 27 #define SKI_BUDGET_X (SKI_TRADER_INVENTORY_BEGIN_X + 9) #define SKI_BUDGET_Y (SKI_FACE_Y + SKI_FACE_HEIGHT + 5) #define SKI_BUDGET_WIDTH 73 #define SKI_BUDGET_OFFSET_TO_VALUE (SKI_BUDGET_Y + 10) // trader inventory item boxes #define SKI_TRADER_INVENTORY_BOXES_BEGIN_X (SKI_TRADER_INVENTORY_BEGIN_X + 166) #define SKI_TRADER_INVENTORY_BOXES_BEGIN_Y (SKI_TRADER_INVENTORY_BEGIN_Y + 30) #define SKI_TRADER_INVENTORY_BOXES_PER_ROW ((SKI_TRADER_INVENTORY_END_X - SKI_TRADER_INVENTORY_BOXES_BEGIN_X) / SKI_INV_OFFSET_X) #define SKI_TRADER_INVENTORY_BOXES_PER_COL ((SKI_TRADER_INVENTORY_END_Y - SKI_TRADER_INVENTORY_BOXES_BEGIN_Y) / SKI_INV_OFFSET_Y) // second: trader offer area #define SKI_TRADER_OFFER_BEGIN_X SKI_TOTAL_BEGIN_X #define SKI_TRADER_OFFER_BEGIN_Y SKI_TRADER_INVENTORY_END_Y #define SKI_TRADER_OFFER_END_X SKI_TOTAL_END_X #define SKI_TRADER_OFFER_END_Y (90 + SKI_TRADER_INVENTORY_END_Y) // bfant: adjusted so 2 rows fit again after item text modification // trader offer area item boxes #define SKI_TRADER_OFFER_BOXES_BEGIN_X (SKI_TRADER_OFFER_BEGIN_X + 91) #define SKI_TRADER_OFFER_BOXES_BEGIN_Y (SKI_TRADER_OFFER_BEGIN_Y + 8) #define SKI_TRADER_OFFER_BOXES_PER_ROW ((SKI_TRADER_OFFER_END_X - SKI_TRADER_OFFER_BOXES_BEGIN_X) / SKI_INV_OFFSET_X) #define SKI_TRADER_OFFER_BOXES_PER_COL ((SKI_TRADER_OFFER_END_Y - SKI_TRADER_OFFER_BOXES_BEGIN_Y) / SKI_INV_OFFSET_Y) #define SKI_TOTAL_COST_X (SKI_TRADER_OFFER_BEGIN_X + 9) #define SKI_TOTAL_COST_Y (SKI_TRADER_OFFER_BEGIN_Y + 35)//159 #define SKI_TOTAL_COST_WIDTH 73 #define SKI_ARMS_DEALER_TOTAL_COST_X (SKI_TRADER_OFFER_BEGIN_X + 16) #define SKI_ARMS_DEALER_TOTAL_COST_Y (SKI_TRADER_OFFER_BEGIN_Y + 51)//191 #define SKI_ARMS_DEALER_TOTAL_COST_WIDTH 59 #define SKI_ARMS_DEALER_TOTAL_COST_HEIGHT 20 // third: player offer area #define SKI_PLAYER_OFFER_BEGIN_X SKI_TOTAL_BEGIN_X #define SKI_PLAYER_OFFER_BEGIN_Y SKI_TRADER_OFFER_END_Y #define SKI_PLAYER_OFFER_END_X SKI_TOTAL_END_X #define SKI_PLAYER_OFFER_END_Y SKI_TOTAL_END_Y // player offer area item boxes #define SKI_PLAYER_OFFER_BOXES_BEGIN_X (SKI_PLAYER_OFFER_BEGIN_X + 91) #define SKI_PLAYER_OFFER_BOXES_BEGIN_Y (SKI_PLAYER_OFFER_BEGIN_Y + 41) #define SKI_PLAYER_OFFER_BOXES_PER_ROW ((SKI_PLAYER_OFFER_END_X - SKI_PLAYER_OFFER_BOXES_BEGIN_X) / SKI_INV_OFFSET_X) #define SKI_PLAYER_OFFER_BOXES_PER_COL ((SKI_PLAYER_OFFER_END_Y - SKI_PLAYER_OFFER_BOXES_BEGIN_Y) / SKI_INV_OFFSET_Y) #define SKI_PLAYERS_CURRENT_BALANCE_X (SKI_PLAYER_OFFER_BEGIN_X + 9) #define SKI_PLAYERS_CURRENT_BALANCE_Y (SKI_PLAYER_OFFER_BEGIN_Y + 35) #define SKI_PLAYERS_CURRENT_BALANCE_WIDTH SKI_TOTAL_COST_WIDTH #define SKI_PLAYERS_CURRENT_BALANCE_OFFSET_TO_VALUE (SKI_PLAYERS_CURRENT_BALANCE_Y + 20) #define SKI_TOTAL_VALUE_X (SKI_PLAYER_OFFER_BEGIN_X + 9) #define SKI_TOTAL_VALUE_Y (SKI_PLAYERS_CURRENT_BALANCE_Y + 50) #define SKI_TOTAL_VALUE_WIDTH SKI_TOTAL_COST_WIDTH #define SKI_TOTAL_VALUE_OFFSET_TO_VALUE (SKI_TOTAL_VALUE_Y + 20) #define SKI_TRANSACTION_BUTTON_X (SKI_PLAYER_OFFER_BEGIN_X + 147)//214 #define SKI_TRANSACTION_BUTTON_Y (SKI_PLAYER_OFFER_BEGIN_Y + 8) #define SKI_DONE_BUTTON_X (SKI_PLAYER_OFFER_BEGIN_X + 292)//414 #define SKI_DONE_BUTTON_Y SKI_TRANSACTION_BUTTON_Y // when colouring the tradescreen, we use this margin #define SKI_MARGIN 4 //////////////////////////////////////////// // // Global Variables // /////////////////////////////////////////// #ifdef JA2UB //ja25 UB enum { TRNSMTR_MSG_BOX__NONE, TRNSMTR_MSG_BOX__1_FRAME_WAIT, TRNSMTR_MSG_BOX__DISPLAY_BOX, }; void AskUserToAttachTransmitterToLaptop(); void AttachLaptopTransmitterToLaptop( UINT8 ubExitValue ); #endif UINT32 guiCornerWhereTacticalIsStillSeenImage; //This image is for where the corner of tactical is still seen through the shop keeper interface BOOLEAN gfSKIScreenEntry = TRUE; BOOLEAN gfSKIScreenExit = FALSE; BOOLEAN gfUserHasRequestedToLeave = FALSE; BOOLEAN gfRenderScreenOnNextLoop = FALSE; UINT8 gubSkiDirtyLevel = SKI_DIRTY_LEVEL0; INT32 giSKIMessageBox=-1; INT8 gbSelectedArmsDealerID = -1; //Contains the enum value for the currently selected arms dealer // Flugente: while we're trading, store the ID of non-NPC dealers SoldierID gusIDOfCivTrader = NOBODY; //the quote that is in progress, in certain circumstances, we don't want queuing of related but different quotes INT32 giShopKeepDialogueEventinProgress = - 1; INVENTORY_IN_SLOT gMoveingItem; OBJECTTYPE *gpHighLightedItemObject=NULL; BOOLEAN gfResetShopKeepIdleQuote = FALSE; BOOLEAN gfDoEvaluationAfterOpening = FALSE; typedef struct { UINT8 ubCurrentPage; UINT8 ubNumberOfPages; UINT16 ubFirstItemIndexOnPage; } SELECTED_ARMS_DEALERS_STATS; SELECTED_ARMS_DEALERS_STATS gSelectArmsDealerInfo; //This pointer is used to store the inventory the arms dealer has for sale std::vector gpTempDealersInventory; INVENTORY_IN_SLOT ArmsDealerOfferArea[ SKI_NUM_TRADING_INV_SLOTS ]; INVENTORY_IN_SLOT PlayersOfferArea[ SKI_NUM_TRADING_INV_SLOTS ]; std::vector subObjects; BOOLEAN gfHavePurchasedItemsFromTony = FALSE; BOOLEAN gfDealerHasSaidTheEvaluateQuoteOnceThisSession=FALSE; BOOLEAN gfAlreadySaidTooMuchToRepair = FALSE; UINT32 guiRandomQuoteDelayTime=SKI_DEALERS_RANDOM_QUOTE_DELAY; //Index for the shopkeepers face INT32 giShopKeeperFaceIndex; //Id for the popup box INT32 giPopUpBoxId=-1; BOOLEAN gfIsTheShopKeeperTalking; #ifdef JA2UB UINT8 gubDisplayMsgBoxAskingUserToAttachTransmitter=0; // Ja25 UB #endif // the glow for unwanted items BOOLEAN fDeltaColorForShopkeepUnwanted = TRUE; INT8 bShopKeepGlowIndex = 9; BOOLEAN fDoShopkeepGlow = FALSE; BOOLEAN gfRemindedPlayerToPickUpHisStuff = FALSE; BOOLEAN gfDoneBusinessThisSession = FALSE; // this is used within SKI exclusively, to handle small faces UINT8 gubArrayOfEmployedMercs[ CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS ]; UINT32 guiSmallSoldiersFace[ CODE_MAXIMUM_NUMBER_OF_PLAYER_SLOTS ]; UINT16 gubNumberMercsInArray; //The subutitled text for what the merc is saying CHAR16 gsShopKeeperTalkingText[ SKI_SUBTITLE_TEXT_SIZE ]; UINT16 gusPositionOfSubTitlesX=0; BOOLEAN gfExitSKIDueToMessageBox=FALSE; OBJECTTYPE *pShopKeeperItemDescObject=NULL; #ifdef JA2UB BOOLEAN gfCanSayMakeTransactionQuote; //ja25 UB #endif UINT32 guiNextFreeInvSlot; BOOLEAN gfStartWithRepairsDelayedQuote = FALSE; BOOLEAN gfPerformTransactionInProgress = FALSE; BOOLEAN gfCommonQuoteUsedThisSession[ NUM_COMMON_SK_QUOTES ]; extern SOLDIERTYPE *gpSMCurrentMerc; extern SOLDIERTYPE *gpItemDescSoldier; extern MOUSE_REGION gItemDescAttachmentRegions[MAX_ATTACHMENTS]; extern MOUSE_REGION gInvDesc; extern BOOLEAN gfSMDisableForItems; extern OBJECTTYPE *gpItemDescObject; extern OBJECTTYPE *gpItemDescPrevObject; extern OBJECTTYPE *gpItemDescPrevObject; extern void HandleShortCutExitState( void ); extern SoldierID gubSelectSMPanelToMerc; extern INT32 giItemDescAmmoButton; extern BOOLEAN BltVSurfaceUsingDD( HVSURFACE hDestVSurface, HVSURFACE hSrcVSurface, UINT32 fBltFlags, INT32 iDestX, INT32 iDestY, RECT *SrcRect ); extern UINT8 gubLastSpecialItemAddedAtElement; // Enums for possible evaluation results enum { EVAL_RESULT_NORMAL, EVAL_RESULT_OK_BUT_REALLY_DAMAGED, EVAL_RESULT_DONT_HANDLE, EVAL_RESULT_WORTHLESS, EVAL_RESULT_NOT_DAMAGED, EVAL_RESULT_NON_REPAIRABLE, EVAL_RESULT_ROCKET_RIFLE, NUM_EVAL_RESULTS }; BOOLEAN gfEvalResultQuoteSaid[ NUM_EVAL_RESULTS ]; UINT32 guiLastTimeDealerSaidNormalEvaluationQuote = 0; BOOLEAN gfSkiDisplayDropItemToGroundText = FALSE; class ITEM_TO_ADD_AFTER_SKI_OPEN { public: ITEM_TO_ADD_AFTER_SKI_OPEN() {initialize();}; void initialize() {fActive = FALSE; ItemObject.initialize();}; BOOLEAN fActive; OBJECTTYPE ItemObject; }; ITEM_TO_ADD_AFTER_SKI_OPEN gItemToAdd; //Page up buttons for the merchants void BtnSKI_InvPageUpButtonCallback(GUI_BUTTON *btn,INT32 reason); UINT32 guiSKI_InvPageUpButton; INT32 guiSKI_InvPageUpButtonImage; //Page down buttons for the merchants void BtnSKI_InvPageDownButtonCallback(GUI_BUTTON *btn,INT32 reason); UINT32 guiSKI_InvPageDownButton; INT32 guiSKI_InvPageDownButtonImage; //Transaction buttons void BtnSKI_TransactionButtonCallback(GUI_BUTTON *btn,INT32 reason); UINT32 guiSKI_TransactionButton; INT32 guiSKI_TransactionButtonImage; //Done buttons void BtnSKI_DoneButtonCallback(GUI_BUTTON *btn,INT32 reason); UINT32 guiSKI_DoneButton; INT32 guiSKI_DoneButtonImage; UINT32 guiItemCrossOut; UINT32 guiMainTradeScreenImage = -1; BOOLEAN gfDisplayNoRoomMsg = FALSE; //Blanket the entire screen MOUSE_REGION gSKI_EntireScreenMouseRegions; MOUSE_REGION gDealersInventoryMouseRegions[ SKI_NUM_ARMS_DEALERS_INV_SLOTS ]; MOUSE_REGION gRepairmanInventorySmallFaceMouseRegions[ SKI_NUM_ARMS_DEALERS_INV_SLOTS ]; int gDealersInventoryActiveRegions = 0; void SelectDealersInventoryRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); void SelectDealersInventoryMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); MOUSE_REGION gDealersOfferSlotsMouseRegions[ SKI_NUM_TRADING_INV_SLOTS ]; MOUSE_REGION gDealersOfferSlotsSmallFaceMouseRegions[SKI_NUM_TRADING_INV_SLOTS]; int gDealersOfferActiveRegions = 0; void SelectDealersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); void SelectDealersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); MOUSE_REGION gPlayersOfferSlotsMouseRegions[ SKI_NUM_TRADING_INV_SLOTS ]; MOUSE_REGION gPlayersOfferSlotsSmallFaceMouseRegions[SKI_NUM_TRADING_INV_SLOTS]; int gPlayersOfferActiveRegions = 0; void SelectPlayersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); void SelectPlayersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); MOUSE_REGION gSkiInventoryMovementAreaMouseRegions; //Mouse region for the subtitles region when the merc is talking MOUSE_REGION gShopKeeperSubTitleMouseRegion; void ShopKeeperSubTitleRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); MOUSE_REGION gArmsDealersFaceMouseRegions; void SelectArmsDealersFaceRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); //Region to allow the user to drop items to the ground MOUSE_REGION gArmsDealersDropItemToGroundMouseRegions; void SelectArmsDealersDropItemToGroundRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); void SelectArmsDealersDropItemToGroundMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ); //////////////////////////////////////////// // // Local Function Prototypes // /////////////////////////////////////////// BOOLEAN EnterShopKeeperInterface(); BOOLEAN ExitShopKeeperInterface(); void HandleShopKeeperInterface(); BOOLEAN RenderShopKeeperInterface(); void GetShopKeeperInterfaceUserInput(); void DestroySkiInventorySlotMouseRegions( ); void CreateSkiInventorySlotMouseRegions( ); void InitializeShopKeeper( BOOLEAN fResetPage ); void CalculateFirstItemIndexOnPage( ); void DisplayArmsDealerCurrentInventoryPage( ); BOOLEAN DetermineArmsDealersSellingInventory( ); void StoreObjectsInNextFreeDealerInvSlot( DEALER_SPECIAL_ITEM* pSpecial, std::vector& pInventory, UINT8 ubOwner ); BOOLEAN RepairIsDone(DEALER_SPECIAL_ITEM* pSpecial); UINT32 DisplayInvSlot( UINT16 ubSlotNum, UINT16 usItemIndex, UINT16 usPosX, UINT16 usPosY, OBJECTTYPE *ItemObject, BOOLEAN fHatchedOut, UINT8 ubItemArea ); void DisplayArmsDealerOfferArea(); INT16 AddItemToArmsDealerOfferArea( INVENTORY_IN_SLOT* pInvSlot, INT16 bSlotIdInOtherLocation ); BOOLEAN RemoveItemFromArmsDealerOfferArea( INT16 bSlotId, BOOLEAN fKeepItem ); void SetSkiRegionHelpText( INVENTORY_IN_SLOT *pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea ); void SetSkiFaceRegionHelpText( INVENTORY_IN_SLOT *pInv, MOUSE_REGION* pRegion, UINT8 ubScreenArea ); void RestoreTacticalBackGround(); void DisplayPlayersOfferArea(); UINT32 CalcShopKeeperItemPrice( BOOLEAN fDealerSelling, BOOLEAN fUnitPriceOnly, UINT16 usItemID, FLOAT dModifier, OBJECTTYPE *pItemObject ); FLOAT ItemConditionModifier(UINT16 usItemIndex, INT16 bStatus); BOOLEAN RemoveItemFromPlayersOfferArea( INT16 bSlotIdInOtherLocation ); UINT32 CalculateTotalArmsDealerCost(); UINT32 CalculateTotalPlayersValue(); void MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea( ); void PerformTransaction( UINT32 uiMoneyFromPlayersAccount ); void MovePlayerOfferedItemsOfValueToArmsDealersInventory( ); INT16 AddInventoryToSkiLocation( INVENTORY_IN_SLOT *pInv, UINT16 ubSpotLocation, UINT8 ubWhere ); BOOLEAN RemoveItemFromDealersInventory( INVENTORY_IN_SLOT* pInvSlot, UINT16 ubSlot ); BOOLEAN InitShopKeepersFace( UINT8 usProfileID ); void DisplayTalkingArmsDealer(); BOOLEAN StartShopKeeperTalking( UINT16 usQuoteNum ); void HandleShopKeeperDialog( UINT8 ubInit ); BOOLEAN IsGunOrAmmoOfSameTypeSelected( OBJECTTYPE *pItemObject ); void RemoveShopKeeperSubTitledText(); BOOLEAN AreThereItemsInTheArmsDealersOfferArea( ); BOOLEAN AreThereItemsInThePlayersOfferArea( ); void ShutUpShopKeeper(); int CountNumberOfValuelessItemsInThePlayersOfferArea( ); int CountNumberOfItemsInThePlayersOfferArea( ); int CountNumberOfItemsInTheArmsDealersOfferArea( ); INT8 GetSlotNumberForMerc( UINT8 ubProfile ); void HandleCurrentModeText( UINT8 ubMode ); void EnableDisableDealersInventoryPageButtons(); void EnableDisableEvaluateAndTransactionButtons(); BOOLEAN IsMoneyTheOnlyItemInThePlayersOfferArea( ); UINT32 CalculateHowMuchMoneyIsInPlayersOfferArea( ); void MovePlayersItemsToBeRepairedToArmsDealersInventory(); BOOLEAN RemoveRepairItemFromDealersOfferArea( INT16 bSlot ); INT8 GetInvSlotOfUnfullMoneyInMercInventory( SOLDIERTYPE *pSoldier ); void ClearPlayersOfferSlot( INT32 ubSlotToClear ); void ClearArmsDealerOfferSlot( INT32 ubSlotToClear ); BOOLEAN DoSkiMessageBox( UINT8 ubStyle, STR16 zString, UINT32 uiExitScreen, UINT8 ubFlags, MSGBOX_CALLBACK ReturnCallback ); BOOLEAN WillShopKeeperRejectObjectsFromPlayer( INT8 bDealerId, INT8 bSlotId ); void CheckAndHandleClearingOfPlayerOfferArea( void ); BOOLEAN CanShopkeeperOverrideDialogue( void ); INT16 GetNumberOfItemsInPlayerOfferArea( void ); void HandleCheckIfEnoughOnTheTable( void ); void InitShopKeeperItemDescBox( OBJECTTYPE *pObject, UINT16 ubPocket, UINT8 ubFromLocation ); void StartSKIDescriptionBox( void ); BOOLEAN ShopkeeperAutoPlaceObject( SOLDIERTYPE * pSoldier, OBJECTTYPE * pObj, BOOLEAN fNewItem ); void ShopkeeperAddItemToPool( INT32 sGridNo, OBJECTTYPE *pObject, INT8 bVisible, UINT8 ubLevel, UINT16 usFlags, INT8 bRenderZHeightAboveLevel ); void IfMercOwnedCopyItemToMercInv( INVENTORY_IN_SLOT *pInv ); void IfMercOwnedRemoveItemFromMercInv( INVENTORY_IN_SLOT *pInv ); void IfMercOwnedRemoveItemFromMercInv2( UINT8 ubOwnerProfileId, INT16 bOwnerSlotId ); void SplitComplexObjectIntoSubObjects( OBJECTTYPE *pComplexObject ); void CountSubObjectsInObject( OBJECTTYPE *pComplexObject, UINT8 *pubRepairableSubObjects, UINT8 *pubNonRepairableSubObjects ); BOOLEAN AddObjectForEvaluation(OBJECTTYPE *pObject, UINT8 ubOwnerProfileId, INT16 bOwnerSlotId, BOOLEAN fFirstOne ); BOOLEAN OfferObjectToDealer( OBJECTTYPE *pComplexObject, UINT8 ubOwnerProfileId, INT16 bOwnerSlotId ); BOOLEAN SKITryToReturnInvToOwnerOrCurrentMerc( INVENTORY_IN_SLOT *pInv ); BOOLEAN SKITryToAddInvToMercsInventory( INVENTORY_IN_SLOT *pInv, SOLDIERTYPE *pSoldier ); void ExitSKIRequested(); void EvaluateItemAddedToPlayersOfferArea( INT8 bSlotID, BOOLEAN fFirstOne ); void ResetAllQuoteSaidFlags(); INVENTORY_IN_SLOT *GetPtrToOfferSlotWhereThisItemIs( UINT8 ubProfileID, INT16 bInvPocket ); void DealWithItemsStillOnTheTable(); void ReturnItemToPlayerSomehow( INVENTORY_IN_SLOT *pInvSlot, SOLDIERTYPE *pDropSoldier ); void GivePlayerSomeChange( UINT32 uiAmount ); void HandlePossibleRepairDelays(); BOOLEAN RepairmanFixingAnyItemsThatShouldBeDoneNow( UINT32 *puiHoursSinceOldestItemRepaired ); void DelayRepairsInProgressBy( UINT32 uiMinutesDelayed ); BOOLEAN CanTheDropItemToGroundStringBeDisplayed(); void DisplayTheSkiDropItemToGroundString(); UINT32 EvaluateInvSlot( INVENTORY_IN_SLOT *pInvSlot ); void BuildItemHelpTextString( CHAR16 sString[], INVENTORY_IN_SLOT *pInv, UINT8 ubScreenArea ); void BuildRepairTimeString( CHAR16 sString[], UINT32 uiTimeInMinutesToFixItem ); void BuildDoneWhenTimeString( CHAR16 sString[], UINT8 ubArmsDealer, INVENTORY_IN_SLOT* pObject ); void DisableAllDealersInventorySlots( void ); void EnableAllDealersInventorySlots( void ); void DisableAllDealersOfferSlots( void ); void EnableAllDealersOfferSlots( void ); void HatchOutInvSlot( UINT16 usPosX, UINT16 usPosY ); #ifdef JA2TESTVERSION BOOLEAN gfTestDisplayDealerCash = FALSE; void DisplayAllDealersCash(); #endif; void INVENTORY_IN_SLOT::initialize() { ItemObject.initialize(); fActive = 0; sItemIndex = 0; uiFlags = 0; ubLocationOfObject = 0; bSlotIdInOtherLocation = 0; ubIdOfMercWhoOwnsTheItem = 0; uiItemPrice = 0; uiRepairDoneTime = 0; } INVENTORY_IN_SLOT& INVENTORY_IN_SLOT::operator=(OLD_INVENTORY_IN_SLOT_101& src) { this->ItemObject = src.oldItemObject; this->fActive = src.fActive; this->sItemIndex = src.sItemIndex; this->uiFlags = src.uiFlags; this->ubLocationOfObject = src.ubLocationOfObject; this->bSlotIdInOtherLocation = src.bSlotIdInOtherLocation; this->ubIdOfMercWhoOwnsTheItem = src.ubIdOfMercWhoOwnsTheItem; this->uiItemPrice = src.uiItemPrice; this->uiRepairDoneTime = 0; return *this; } //ppp // // screen handler functions // UINT32 ShopKeeperScreenInit() { //Set so next time we come in, we can set up gfSKIScreenEntry = TRUE; return( TRUE ); } UINT32 ShopKeeperScreenHandle() { StartFrameBufferRender(); if( gfSKIScreenEntry ) { PauseGame(); if( !EnterShopKeeperInterface() ) { gfSKIScreenExit = TRUE; EnterTacticalScreen( ); //ADB if we fail, unpause the game UnPauseGame(); return( SHOPKEEPER_SCREEN ); } else { gfSKIScreenEntry = FALSE; gfSKIScreenExit = FALSE; } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; gfRenderScreenOnNextLoop = TRUE; InvalidateRegion( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); } if( gfRenderScreenOnNextLoop ) gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; RestoreBackgroundRects(); GetShopKeeperInterfaceUserInput(); // Check for any newly added items... if ( gpSMCurrentMerc->flags.fCheckForNewlyAddedItems ) { // Startup any newly added items.... CheckForAnyNewlyAddedItems( gpSMCurrentMerc ); gpSMCurrentMerc->flags.fCheckForNewlyAddedItems = FALSE; } HandleShopKeeperInterface(); if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 ) { RenderShopKeeperInterface(); fInterfacePanelDirty = DIRTYLEVEL2; gubSkiDirtyLevel = SKI_DIRTY_LEVEL0; } else if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL1 ) { fInterfacePanelDirty = DIRTYLEVEL2; gubSkiDirtyLevel = SKI_DIRTY_LEVEL0; } // render buttons marked dirty DisableSMPpanelButtonsWhenInShopKeeperInterface( FALSE ); RenderButtons( ); //CHRISL: If we put this function call here, we overwrite the bullet graphic. I don't know why. I just know it happens. //RenderItemDescriptionBox( ); // render help SaveBackgroundRects( ); RenderButtonsFastHelp( ); ExecuteBaseDirtyRectQueue(); EndFrameBufferRender(); if( gfSKIScreenExit ) { ExitShopKeeperInterface(); gfSKIScreenExit = FALSE; gfSKIScreenEntry = TRUE; EnterTacticalScreen( ); UnPauseGame(); } if ( gfDisplayNoRoomMsg ) { // tell player there's not enough room in the player's offer area // ARM: message is delayed because we need the mouse restriction to be in place BEFORE it comes up so it gets lifted/restored DoSkiMessageBox( MSG_BOX_BASIC_STYLE, SKI_Text[ SKI_TEXT_NO_MORE_ROOM_IN_PLAYER_OFFER_AREA ], SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, NULL ); gfDisplayNoRoomMsg = FALSE; } return( SHOPKEEPER_SCREEN ); } UINT32 ShopKeeperScreenShutdown() { ShutDownArmsDealers(); return( TRUE ); } BOOLEAN EnterShopKeeperInterface() { VOBJECT_DESC VObjectDesc; UINT16 ubCnt; CHAR8 zTemp[32]; VSURFACE_DESC vs_desc; //ADB if we are here, we must be able to talk with an extended ear (CheckIfRadioIsEquipped()) //but if we are physically too far away, we don't have extended arms! SOLDIERTYPE* pSoldier = gusSelectedSoldier; SOLDIERTYPE* pShopkeeper = NULL; if ( gusIDOfCivTrader != NOBODY ) pShopkeeper = gusIDOfCivTrader; else pShopkeeper = FindSoldierByProfileID( armsDealerInfo[gbSelectedArmsDealerID].ubShopKeeperID, FALSE ); if (PythSpacesAway( pSoldier->sGridNo, pShopkeeper->sGridNo ) > NPC_TALK_RADIUS ) { //so now we know we are too far away to trade, so instead of just quitting, //either post a message or run to the guy like HandleTalkInit does if( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, L"Unable to interact with shopkeeper during combat" ); return( FALSE ); } else { // First calculate APs and validate... // Check AP cost... if ( !EnoughPoints( pSoldier, APBPConstants[AP_TALK], 0, TRUE ) ) { return( FALSE ); } // First get an adjacent gridno.... UINT8 ubDirection; INT32 sActionGridNo = FindAdjacentGridEx( pSoldier, pShopkeeper->sGridNo, &ubDirection, NULL, FALSE, TRUE ); if ( sActionGridNo == -1 ) { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ NO_PATH ] ); return( FALSE ); } if ( UIPlotPath( pSoldier, sActionGridNo, NO_COPYROUTE, FALSE, TEMPORARY, pSoldier->usUIMovementMode, NOT_STEALTH, FORWARD, pSoldier->bActionPoints ) == 0 ) { ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_UI_FEEDBACK, TacticalStr[ NO_PATH ] ); return( FALSE ); } // Walk up and talk to buddy.... gfNPCCircularDistLimit = TRUE; INT32 sGoodGridNo = FindGridNoFromSweetSpotWithStructData( pSoldier, pSoldier->usUIMovementMode, pShopkeeper->sGridNo, (NPC_TALK_RADIUS-1), &ubDirection, TRUE ); gfNPCCircularDistLimit = FALSE; // Now walkup to talk.... pSoldier->aiData.ubPendingAction = MERC_TALK; pSoldier->aiData.uiPendingActionData1 = pShopkeeper->ubID; pSoldier->aiData.ubPendingActionAnimCount = 0; // WALK UP TO DEST FIRST pSoldier->EVENT_InternalGetNewSoldierPath(sGoodGridNo, pSoldier->usUIMovementMode , TRUE , pSoldier->flags.fNoAPToFinishMove ); return( FALSE ); } } // make sure current merc is close enough and eligible to talk to the shopkeeper. AssertMsg( CanMercInteractWithSelectedShopkeeper( gusSelectedSoldier ), "Selected merc can't interact with shopkeeper. Send save AM-1"); // Create a video surface to blt corner of the tactical screen that still shines through vs_desc.fCreateFlags = VSURFACE_CREATE_DEFAULT | VSURFACE_SYSTEM_MEM_USAGE; //Heinz: 22.02.09 BUGFIX: best way - to update whole tactical screen around SKI //vs_desc.usWidth = SKI_TACTICAL_BACKGROUND_START_WIDTH; //vs_desc.usHeight = SKI_TACTICAL_BACKGROUND_START_HEIGHT; vs_desc.usWidth = SCREEN_WIDTH; vs_desc.usHeight = SCREEN_HEIGHT; vs_desc.ubBitDepth = 16; if( !AddVideoSurface( &vs_desc, &guiCornerWhereTacticalIsStillSeenImage) ) { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to create Surface where tactical map shows through" ); #endif return( FALSE ); } //Heinz: 22.02.09 shadowing of tactical screen for further using as a background ShadowVideoSurfaceRect( guiCornerWhereTacticalIsStillSeenImage, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); //Clear out all the save background rects EmptyBackgroundRects( ); ShadowVideoSurfaceRect( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT - INV_INTERFACE_HEIGHT ); if( gfExitSKIDueToMessageBox ) { gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; gfExitSKIDueToMessageBox = FALSE; } // silversurfer: added this to prevent random crashes when the temp vector wasn't initialized properly. // This is called in InitializeShopKeeper() a few lines below anyway so it shouldn't hurt here. gpTempDealersInventory.clear(); //Check to make sure the inventory is null ( should always be null if we are just coming in to the SKI ) Assert( gpTempDealersInventory.empty() == true ); //Reinitialize the team panel to be the SM panel SetCurrentInterfacePanel( SM_PANEL ); SetCurrentTacticalPanelCurrentMerc( gusSelectedSoldier ); SetSMPanelCurrentMerc( gusSelectedSoldier ); // load the Main trade screen background image VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP("InterFace\\itemcrossout.sti", VObjectDesc.ImageFile); if( !AddVideoObject(&VObjectDesc, &guiItemCrossOut )) { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to load itemcrossout.sti" ); #endif return( FALSE ); } //Create an array of all mercs (anywhere!) currently in the player's employ, and load their small faces // 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 gubNumberMercsInArray = 0; for( ubCnt = gTacticalStatus.Team[ OUR_TEAM ].bFirstID; ubCnt <= gTacticalStatus.Team[ OUR_TEAM ].bLastID; ++ubCnt ) { pSoldier = MercPtrs[ ubCnt ]; if( pSoldier->bActive && ( pSoldier->ubProfile != NO_PROFILE ) && !(pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !AM_A_ROBOT( pSoldier ) ) { // remember whose face is in this slot gubArrayOfEmployedMercs[ gubNumberMercsInArray ] = pSoldier->ubProfile; //Create the string for the face file name if ( gMercProfiles[pSoldier->ubProfile].Type == PROFILETYPE_IMP ) { sprintf( zTemp, "IMPFACES\\33FACE\\%02d.sti", gMercProfiles[ pSoldier->ubProfile ].ubFaceIndex ); } else { sprintf( zTemp, "FACES\\33FACE\\%02d.sti", gMercProfiles[ pSoldier->ubProfile ].ubFaceIndex ); } //While we are at it, add their small face VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP( zTemp, VObjectDesc.ImageFile); if(!AddVideoObject(&VObjectDesc, &guiSmallSoldiersFace[ gubNumberMercsInArray ] )) { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to load %s", zTemp ); #endif return( FALSE ); } ++gubNumberMercsInArray; } } //Load the graphic for the arrow button guiSKI_InvPageUpButtonImage = LoadButtonImage("INTERFACE\\TradeScrollArrows.sti", -1,0,-1,1,-1 ); guiSKI_InvPageDownButtonImage = UseLoadedButtonImage( guiSKI_InvPageUpButtonImage, -1,2,-1,3,-1 ); //Page up button for the merchant inventory guiSKI_InvPageUpButton = QuickCreateButton( guiSKI_InvPageUpButtonImage, SKI_PAGE_UP_ARROWS_X, SKI_PAGE_UP_ARROWS_Y, BUTTON_NEWTOGGLE, MSYS_PRIORITY_HIGHEST, DEFAULT_MOVE_CALLBACK, BtnSKI_InvPageUpButtonCallback ); SpecifyDisabledButtonStyle( guiSKI_InvPageUpButton, DISABLED_STYLE_HATCHED ); //Page down button for the merchant inventory guiSKI_InvPageDownButton = QuickCreateButton( guiSKI_InvPageDownButtonImage, SKI_PAGE_DOWN_ARROWS_X, SKI_PAGE_DOWN_ARROWS_Y, BUTTON_NEWTOGGLE, MSYS_PRIORITY_HIGHEST, DEFAULT_MOVE_CALLBACK, BtnSKI_InvPageDownButtonCallback ); SpecifyDisabledButtonStyle( guiSKI_InvPageDownButton, DISABLED_STYLE_HATCHED ); EnableDisableShopkeeperButtons(SHOPKEEPER_SCREEN, ACTIVATE_BUTTON); //Blanket the entire screen MSYS_DefineRegion( &gSKI_EntireScreenMouseRegions, 0, 0, (SCREEN_WIDTH - 1), (SCREEN_HEIGHT - INV_INTERFACE_HEIGHT), MSYS_PRIORITY_HIGH-2, CURSOR_NORMAL, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK); MSYS_AddRegion( &gSKI_EntireScreenMouseRegions ); //Create the mouse regions for the inventory slot CreateSkiInventorySlotMouseRegions( ); //Create the mouse region to limit the movement of the item cursos 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, CURSOR_NORMAL, MSYS_NO_CALLBACK, MSYS_NO_CALLBACK ); //SelectSkiInventoryMovementAreaRegionCallBack MSYS_AddRegion( &gSkiInventoryMovementAreaMouseRegions ); //Disable the region that limits the movement of the cursor with the item MSYS_DisableRegion( &gSkiInventoryMovementAreaMouseRegions ); //Create the mouse region for the shopkeeper's face MSYS_DefineRegion( &gArmsDealersFaceMouseRegions, SKI_FACE_X, SKI_FACE_Y, (UINT16)(SKI_FACE_X+SKI_FACE_WIDTH), (UINT16)(SKI_FACE_Y+SKI_FACE_HEIGHT), MSYS_PRIORITY_HIGH-1, CURSOR_NORMAL, MSYS_NO_CALLBACK, SelectArmsDealersFaceRegionCallBack ); MSYS_AddRegion( &gArmsDealersFaceMouseRegions ); for (int x = 0; x < SKI_NUM_TRADING_INV_SLOTS; ++x) { ArmsDealerOfferArea[x].initialize(); PlayersOfferArea[x].initialize(); } if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { HandlePossibleRepairDelays(); } //Setup the currently selected arms dealer InitializeShopKeeper( TRUE ); //Set the flag indicating that we are in the shop keeper interface guiTacticalInterfaceFlags |= INTERFACE_SHOPKEEP_INTERFACE; gMoveingItem.initialize(); memset( &gfCommonQuoteUsedThisSession, FALSE, sizeof( gfCommonQuoteUsedThisSession ) ); //Init the shopkeepers face InitShopKeepersFace( armsDealerInfo[ gbSelectedArmsDealerID ].ubShopKeeperID ); gfDoneBusinessThisSession = FALSE; //Call this to set the fact that we just entered the screen HandleShopKeeperDialog( 1 ); ResetAllQuoteSaidFlags(); //Reset the highlighted item pointer; gpHighLightedItemObject = NULL; //Reset gfRemindedPlayerToPickUpHisStuff = FALSE; gfUserHasRequestedToLeave = FALSE; gfDisplayNoRoomMsg = FALSE; //Disable the map radar region MSYS_DisableRegion(&gRadarRegion); gfDoEvaluationAfterOpening = FALSE; if( gItemToAdd.fActive ) { bool fAddedOK = false; //if this is NOT a repair dealer or he is is but there is enough space in the player's offer area // (you can't be out of space if it isn't a repairman, only they can fill it up with repaired items!) if( ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) || (CountNumberOfItemsInThePlayersOfferArea( ) < (SKI_TRADER_INVENTORY_BOXES_PER_ROW * SKI_TRADER_INVENTORY_BOXES_PER_COL)) ) { if ( OfferObjectToDealer( &(gItemToAdd.ItemObject), gpSMCurrentMerc->ubProfile, NO_SLOT ) ) { fAddedOK = true; } } if ( fAddedOK == true ) { // evaluate it gfDoEvaluationAfterOpening = TRUE; } else { //add the item back to the current PC into the slot it came from //ADB screw slot, slot is (used to be, before it was deleted) only ever, so autoplace it AutoPlaceObject(gpSMCurrentMerc->ubID, &gItemToAdd.ItemObject, FALSE); } //Clear the contents of the structure gItemToAdd.initialize(); } // Dirty the bottom panel fInterfacePanelDirty = DIRTYLEVEL2; gfDealerHasSaidTheEvaluateQuoteOnceThisSession = FALSE; guiRandomQuoteDelayTime = SKI_DEALERS_RANDOM_QUOTE_DELAY; pShopKeeperItemDescObject = NULL; //Region to allow the user to drop items to the ground MSYS_DefineRegion( &gArmsDealersDropItemToGroundMouseRegions, SKI_DROP_ITEM_TO_GROUND_START_X, SKI_DROP_ITEM_TO_GROUND_START_Y, SCREEN_WIDTH - 1, SCREEN_HEIGHT - INV_INTERFACE_HEIGHT, MSYS_PRIORITY_HIGH, CURSOR_NORMAL, SelectArmsDealersDropItemToGroundMovementRegionCallBack, SelectArmsDealersDropItemToGroundRegionCallBack ); // CURSOR_NORMAL, MSYS_NO_CALLBACK, SelectArmsDealersDropItemToGroundRegionCallBack ); MSYS_AddRegion( &gArmsDealersDropItemToGroundMouseRegions ); gfSkiDisplayDropItemToGroundText = FALSE; // by default re-enable calls to PerformTransaction() gfPerformTransactionInProgress = FALSE; #ifdef JA2UB //JA25 UB //if the dealer is RAUL if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //set the fact the raul refreshed his inventory SetFactFalse( FACT_RAULS_INVENTORY_CHANGED_SINCE_LAST_VISIT ); } #endif return( TRUE ); } BOOLEAN InitShopKeepersFace( UINT8 usProfileID ) { SOLDIERTYPE *pSoldier = NULL; // if this is a non-NPC, don't set this up if ( gusIDOfCivTrader == NOBODY ) pSoldier = FindSoldierByProfileID( armsDealerInfo[gbSelectedArmsDealerID].ubShopKeeperID, FALSE ); if( pSoldier == NULL ) { //Create the facial index giShopKeeperFaceIndex = InitFace( usProfileID, NOBODY, FACE_BIGFACE ); } else { //Create the facial index giShopKeeperFaceIndex = InitFace( usProfileID, pSoldier->ubID, FACE_BIGFACE ); } SetAutoFaceActive( FRAME_BUFFER, FACE_AUTO_RESTORE_BUFFER, giShopKeeperFaceIndex, SKI_FACE_X, SKI_FACE_Y ); //Set it so the face cannot be set InActive if ( giShopKeeperFaceIndex > -1 ) gFacesData[ giShopKeeperFaceIndex ].uiFlags |= FACE_INACTIVE_HANDLED_ELSEWHERE; RenderAutoFace( giShopKeeperFaceIndex ); return(TRUE); } BOOLEAN ExitShopKeeperInterface() { if( gfExitSKIDueToMessageBox ) { gfSKIScreenExit = FALSE; // gfExitSKIDueToMessageBox = FALSE; } if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL ) { DeleteItemDescriptionBox( ); } FreeMouseCursor( TRUE ); //Delete the main shopkeep background DeleteVideoObjectFromIndex( guiItemCrossOut ); DeleteVideoSurfaceFromIndex( guiCornerWhereTacticalIsStillSeenImage ); if ( guiMainTradeScreenImage != -1 ) { DeleteVideoSurfaceFromIndex( guiMainTradeScreenImage ); guiMainTradeScreenImage = -1; } ShutUpShopKeeper(); UnloadButtonImage( guiSKI_InvPageUpButtonImage ); UnloadButtonImage( guiSKI_InvPageDownButtonImage ); //loop through the area and delete small faces for (UINT16 ubCnt=0; ubCnt -1 ) { TacticalCharacterDialogue( &Menptr[bSoldierID], QUOTE_JA2UB_LAPTOP_FIXED ); } gJa25SaveStruct.uiJa25GeneralFlags |= JA_GF__PLAYER_SAID_LAPTOP_FIXED_QUOTE; } #endif return( TRUE ); } void HandleShopKeeperInterface() { UINT8 ubStatusOfSkiRenderDirtyFlag = gubSkiDirtyLevel; INT32 iCounter = 0; //if we are in the item desc panel, disable the buttons if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL ) { DisableSMPpanelButtonsWhenInShopKeeperInterface( FALSE ); DisableButton( guiSKI_InvPageUpButton ); DisableButton( guiSKI_InvPageDownButton ); EnableDisableShopkeeperButtons(SHOPKEEPER_SCREEN, DISABLE_BUTTON); //DisableButton( guiSKI_TransactionButton ); //DisableButton( guiSKI_DoneButton ); //make sure the buttons dont render // ButtonList[ guiSKI_InvPageUpButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; // ButtonList[ guiSKI_InvPageDownButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; //Heinz: 22.02.09 BUGFIX: buttons should render for items in inventory with EDB //ButtonList[ guiSKI_TransactionButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; //ButtonList[ guiSKI_DoneButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; // make sure the shop keeper doesn't start talking ( reset the timing variable ) HandleShopKeeperDialog( 2 ); return; } if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 ) { fInterfacePanelDirty = DIRTYLEVEL2; } RenderTacticalInterface( ); if ( InItemStackPopup( ) ) { RenderItemStackPopup( ( BOOLEAN )( fInterfacePanelDirty == DIRTYLEVEL2 ) ); } // handle check if enough on the table HandleCheckIfEnoughOnTheTable( ); // Render view window fInterfacePanelDirty = DIRTYLEVEL2; RenderRadarScreen( ); if( fInterfacePanelDirty == DIRTYLEVEL2 ) { fInterfacePanelDirty = DIRTYLEVEL0; } DisplayTalkingArmsDealer(); MarkButtonsDirty( ); if( gfDoEvaluationAfterOpening ) { BOOLEAN fFirstOne = TRUE; gfDoEvaluationAfterOpening = FALSE; for ( iCounter = 0; iCounter < gPlayersOfferActiveRegions; ++iCounter ) { if( PlayersOfferArea[ iCounter ].fActive ) { EvaluateItemAddedToPlayersOfferArea( ( INT8 ) iCounter, fFirstOne ); fFirstOne = FALSE; } } gfAlreadySaidTooMuchToRepair = FALSE; } #ifdef JA2TESTVERSION if ( gfTestDisplayDealerCash ) DisplayAllDealersCash(); #endif //if the Ski dirty flag was changed to a lower value, make sure it is set properly if( ubStatusOfSkiRenderDirtyFlag > gubSkiDirtyLevel ) gubSkiDirtyLevel = ubStatusOfSkiRenderDirtyFlag; //if the merc is talking and there is an item currently being highlighted // ( this gets rid of the item burning through the dealers text box ) if( gfIsTheShopKeeperTalking ) //Heinz: 23.02.09 BUGFIX: if shopkeeper is talking we should render popup in all cases //if( gpHighLightedItemObject != NULL || gubSkiDirtyLevel != SKI_DIRTY_LEVEL0 ) RenderMercPopUpBoxFromIndex( giPopUpBoxId, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER); //if we are to display the drop item to ground text if( gfSkiDisplayDropItemToGroundText ) { DisplayTheSkiDropItemToGroundString(); } #ifdef JA2UB //ja25 UB else if( gubDisplayMsgBoxAskingUserToAttachTransmitter != TRNSMTR_MSG_BOX__NONE ) { if( gubDisplayMsgBoxAskingUserToAttachTransmitter == TRNSMTR_MSG_BOX__1_FRAME_WAIT ) { gubDisplayMsgBoxAskingUserToAttachTransmitter = TRNSMTR_MSG_BOX__DISPLAY_BOX; } else if( gubDisplayMsgBoxAskingUserToAttachTransmitter == TRNSMTR_MSG_BOX__DISPLAY_BOX ) { //Tell user that they are attaching the transmitter AskUserToAttachTransmitterToLaptop(); //clear the flag gubDisplayMsgBoxAskingUserToAttachTransmitter = TRNSMTR_MSG_BOX__NONE; } } #endif } // Internal function to draw shop keeper inventory boxes void DrawShopKeeperInventoryBox( INT32 usPosX, INT32 usPosY ) { static const UINT16 col_itembox = Get16BPPColor( FROMRGB( 74, 65, 49 ) ); static const UINT16 col_itemdescription = Get16BPPColor( FROMRGB( 181, 150, 74 ) ); static const UINT16 col_itemstatusbar = Get16BPPColor( FROMRGB( 57, 56, 41 ) ); // draw background for status bar ColorFillVideoSurfaceArea( FRAME_BUFFER, usPosX, usPosY, usPosX + 4, usPosY + 28, col_itemstatusbar ); // draw background for picture of item, etc. ColorFillVideoSurfaceArea( FRAME_BUFFER, usPosX + 4, usPosY, usPosX + SKI_INV_SLOT_WIDTH, usPosY + 28, col_itembox ); // draw background for money area ColorFillVideoSurfaceArea( FRAME_BUFFER, usPosX, usPosY + 28, usPosX + SKI_INV_SLOT_WIDTH, usPosY + SKI_INV_SLOT_HEIGHT, col_itemdescription ); } BOOLEAN RenderShopKeeperInterface() { CHAR16 zMoney[128]; HVSURFACE hDestVSurface, hSrcVSurface; SGPRect SrcRect; if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL ) { return( TRUE ); } // wipe everything once and then redraw if ( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 ) { //Restore the area before blitting the new data on RestoreExternBackgroundRect( SKI_TOTAL_BEGIN_X, SKI_TOTAL_BEGIN_Y, SKI_TOTAL_END_X - SKI_TOTAL_BEGIN_X, SKI_TOTAL_END_Y - SKI_TOTAL_BEGIN_Y ); } UINT16 col_tradescreenmargin = Get16BPPColor( FROMRGB( 64, 64, 64 ) ); UINT16 col_tradescreen = Get16BPPColor( FROMRGB( 255, 0, 255 ) ); // we need to do the colouring twice to colour the margin differently ColorFillVideoSurfaceArea( FRAME_BUFFER, SKI_TOTAL_BEGIN_X, SKI_TOTAL_BEGIN_Y, SKI_TOTAL_END_X, SKI_TOTAL_END_Y, col_tradescreenmargin ); // Flugente: because people find simple plane backgrounds to be offputting, we try for something more fancy // we use a very huge background picture and then display parts of it. // Given that the default picture has a size of 3088x2056, you would need a gigantic resolution for this to not work out. // Flugente: guiMainTradeScreenImage is unsigned as the functions require UINT32. As it is unlikely we will ever use 2^32 pictures, this works well enough if ( guiMainTradeScreenImage == -1 ) { VSURFACE_DESC vs_desc; vs_desc.fCreateFlags = VSURFACE_CREATE_FROMFILE | VSURFACE_SYSTEM_MEM_USAGE; strcpy( vs_desc.ImageFile, "LAPTOP\\shopkeeperbackground.pcx" ); if ( FileExists( vs_desc.ImageFile ) ) { AddVideoSurface( &vs_desc, &guiMainTradeScreenImage ); } } if ( guiMainTradeScreenImage != -1 ) { HVSURFACE hSrcVSurface; UINT32 uiDestPitchBYTES; UINT32 uiSrcPitchBYTES; UINT16 *pDestBuf; UINT8 *pSrcBuf; SGPRect clip; // get surfaces pDestBuf = (UINT16*)LockVideoSurface( FRAME_BUFFER, &uiDestPitchBYTES ); GetVideoSurface( &hSrcVSurface, guiMainTradeScreenImage ); pSrcBuf = LockVideoSurface( guiMainTradeScreenImage, &uiSrcPitchBYTES ); // for our 3 regions we display only parts of the picture clip.iLeft = SKI_TRADER_INVENTORY_BEGIN_X + SKI_MARGIN; clip.iRight = SKI_TRADER_INVENTORY_END_X - SKI_MARGIN; clip.iTop = SKI_TRADER_INVENTORY_BEGIN_Y + SKI_MARGIN; clip.iBottom = SKI_TRADER_INVENTORY_END_Y - SKI_MARGIN; Blt8BPPDataSubTo16BPPBuffer( pDestBuf, uiDestPitchBYTES, hSrcVSurface, pSrcBuf, uiSrcPitchBYTES, SKI_TRADER_INVENTORY_BEGIN_X + SKI_MARGIN, SKI_TRADER_INVENTORY_BEGIN_Y + SKI_MARGIN, &clip ); clip.iLeft = SKI_TRADER_OFFER_BEGIN_X + SKI_MARGIN; clip.iRight = SKI_TRADER_OFFER_END_X - SKI_MARGIN; clip.iTop = SKI_TRADER_OFFER_BEGIN_Y + SKI_MARGIN; clip.iBottom = SKI_TRADER_OFFER_END_Y - SKI_MARGIN; Blt8BPPDataSubTo16BPPBuffer( pDestBuf, uiDestPitchBYTES, hSrcVSurface, pSrcBuf, uiSrcPitchBYTES, SKI_TRADER_OFFER_BEGIN_X + SKI_MARGIN, SKI_TRADER_OFFER_BEGIN_Y + SKI_MARGIN, &clip ); clip.iLeft = SKI_PLAYER_OFFER_BEGIN_X + SKI_MARGIN; clip.iRight = SKI_PLAYER_OFFER_END_X - SKI_MARGIN; clip.iTop = SKI_PLAYER_OFFER_BEGIN_Y + SKI_MARGIN; clip.iBottom = SKI_PLAYER_OFFER_END_Y - SKI_MARGIN; Blt8BPPDataSubTo16BPPBuffer( pDestBuf, uiDestPitchBYTES, hSrcVSurface, pSrcBuf, uiSrcPitchBYTES, SKI_PLAYER_OFFER_BEGIN_X + SKI_MARGIN, SKI_PLAYER_OFFER_BEGIN_Y + SKI_MARGIN, &clip ); // release surfaces UnLockVideoSurface( guiMainTradeScreenImage ); UnLockVideoSurface( FRAME_BUFFER ); } // if we cannot find the picture, use a fallback solution else { ColorFillVideoSurfaceArea( FRAME_BUFFER, SKI_TRADER_INVENTORY_BEGIN_X + SKI_MARGIN, SKI_TRADER_INVENTORY_BEGIN_Y + SKI_MARGIN, SKI_TRADER_INVENTORY_END_X - SKI_MARGIN, SKI_TRADER_INVENTORY_END_Y - SKI_MARGIN, col_tradescreen ); ColorFillVideoSurfaceArea( FRAME_BUFFER, SKI_TRADER_OFFER_BEGIN_X + SKI_MARGIN, SKI_TRADER_OFFER_BEGIN_Y + SKI_MARGIN, SKI_TRADER_OFFER_END_X - SKI_MARGIN, SKI_TRADER_OFFER_END_Y - SKI_MARGIN, col_tradescreen ); ColorFillVideoSurfaceArea( FRAME_BUFFER, SKI_PLAYER_OFFER_BEGIN_X + SKI_MARGIN, SKI_PLAYER_OFFER_BEGIN_Y + SKI_MARGIN, SKI_PLAYER_OFFER_END_X - SKI_MARGIN, SKI_PLAYER_OFFER_END_Y - SKI_MARGIN, col_tradescreen ); } { int usPosY = SKI_TRADER_INVENTORY_BOXES_BEGIN_Y; for ( int y = 0; y -1 ) RenderAutoFace( giShopKeeperFaceIndex ); // Flugente: if this is a non-NPC dealer, we instead show their body in their current animation else if ( gusIDOfCivTrader != NOBODY ) { SOLDIERTYPE* pSoldier = gusIDOfCivTrader; if ( pSoldier ) { UINT32 uiPitch; // Lock video surface UINT16* pBuffer = (UINT16*)LockVideoSurface( FRAME_BUFFER, &uiPitch ); if ( pBuffer != NULL ) { UINT16 usAnimSurface = GetSoldierAnimationSurface( pSoldier, pSoldier->usAnimState ); HVOBJECT hVObject = gAnimSurfaceDatabase[usAnimSurface].hVideoObject; ETRLEObject* pTrav = &(hVObject->pETRLEObject[pSoldier->usAniFrame]); INT32 sCenX = SKI_FACE_X + (INT16)(abs( (long)(SKI_FACE_WIDTH - pTrav->usWidth) ) / 2) - pTrav->sOffsetX; INT32 sCenY = SKI_FACE_Y + (INT16)(abs( (long)(SKI_FACE_HEIGHT - pTrav->usHeight) ) / 2) - pTrav->sOffsetY; SGPRect rect; rect.iLeft = SKI_FACE_X; rect.iTop = SKI_FACE_Y; rect.iRight = SKI_FACE_X + SKI_FACE_WIDTH; rect.iBottom = SKI_FACE_Y + SKI_FACE_HEIGHT; Blt8BPPDataTo16BPPBufferTransShadowClip( pBuffer, uiPitch, hVObject, sCenX, sCenY, pSoldier->usAniFrame, &rect, pSoldier->pShades[pSoldier->ubFadeLevel], TRUE ); UnLockVideoSurface( FRAME_BUFFER ); } } } // CHRISL: If in NIV mode, don't display the clock if ( UsingNewInventorySystem( ) == false ) RenderClock( INTERFACE_CLOCK_X, INTERFACE_CLOCK_Y ); // CHRISL: Added X,Y parameters to allow control of TownID string placement. RenderTownIDString( LOCATION_NAME_X, LOCATION_NAME_Y ); //Display the Title 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, FALSE, CENTER_JUSTIFIED ); // display shopkeeper budget DisplayWrappedString( SKI_BUDGET_X, SKI_BUDGET_Y, SKI_BUDGET_WIDTH, 2, SKI_LABEL_FONT, SKI_TITLE_COLOR, SKI_Text[SKI_TEXT_BUDGET], FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); swprintf( zMoney, L"%d", gArmsDealerStatus[gbSelectedArmsDealerID].uiArmsDealersCash ); InsertCommasForDollarFigure( zMoney ); CHAR16 zTemp2[64]; if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) swprintf( zTemp2, L"%s Intel", zMoney ); else swprintf( zTemp2, L"$%s", zMoney ); DrawTextToScreen( zTemp2, SKI_BUDGET_X, SKI_BUDGET_OFFSET_TO_VALUE, SKI_BUDGET_WIDTH, FONT10ARIAL, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, TRUE, CENTER_JUSTIFIED ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { //Display the Repair cost text 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, FALSE, CENTER_JUSTIFIED); } else { //Display the Total cost text 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, FALSE, CENTER_JUSTIFIED); } //Display the players current balance text 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, FALSE, CENTER_JUSTIFIED ); //Display the players current balance value if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) { swprintf( zMoney, L"%d", (int)(GetIntel()) ); InsertCommasForDollarFigure( zMoney ); swprintf( zTemp2, L"%s Intel", zMoney ); } else { swprintf( zMoney, L"%d", LaptopSaveInfo.iCurrentBalance ); InsertCommasForDollarFigure( zMoney ); swprintf( zTemp2, L"$%s", zMoney ); } DrawTextToScreen( zTemp2, 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, TRUE, CENTER_JUSTIFIED ); //Display the total value text 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, FALSE, CENTER_JUSTIFIED ); //Heinz: 22.02.09 BUGFIX: best way - to update whole tactical screen around SKI //BlitBufferToBuffer( guiRENDERBUFFER, guiSAVEBUFFER, 0, 0, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_HEIGHT ); BlitBufferToBuffer( guiRENDERBUFFER, guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); //At this point the background is pure, copy it to the save buffer if( gfRenderScreenOnNextLoop ) { // BlitBufferToBuffer( guiRENDERBUFFER, guiCornerWhereTacticalIsStillSeenImage, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, SKI_TACTICAL_BACKGROUND_START_WIDTH, SKI_TACTICAL_BACKGROUND_START_HEIGHT ); GetVideoSurface( &hDestVSurface, guiCornerWhereTacticalIsStillSeenImage ); GetVideoSurface( &hSrcVSurface, guiSAVEBUFFER ); //Heinz: 22.02.09 BUGFIX: best way - to update whole tactical screen around SKI //SrcRect.iLeft = SKI_TACTICAL_BACKGROUND_START_X; //SrcRect.iTop = SKI_TACTICAL_BACKGROUND_START_Y; //SrcRect.iRight = SKI_TACTICAL_BACKGROUND_START_X + SKI_TACTICAL_BACKGROUND_START_WIDTH; //SrcRect.iBottom = SKI_TACTICAL_BACKGROUND_START_Y + SKI_TACTICAL_BACKGROUND_START_HEIGHT; //BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, SKI_TACTICAL_BACKGROUND_START_X, SKI_TACTICAL_BACKGROUND_START_Y, (RECT*)&SrcRect ); SrcRect.iLeft = 0; SrcRect.iTop = 0; SrcRect.iRight = SCREEN_WIDTH; SrcRect.iBottom = SCREEN_HEIGHT; BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, 0, 0, (RECT*)&SrcRect ); gfRenderScreenOnNextLoop = FALSE; } DisplayArmsDealerCurrentInventoryPage( ); DisplayArmsDealerOfferArea( ); DisplayPlayersOfferArea( ); EnableDisableEvaluateAndTransactionButtons( ); //if the merc is talking and the screen has been dirtied if( gfIsTheShopKeeperTalking ) RenderMercPopUpBoxFromIndex( giPopUpBoxId, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER); //Restore the tactical background that is visble behind the SKI panel RestoreTacticalBackGround(); InvalidateRegion( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); return( TRUE ); } void RestoreTacticalBackGround() { HVSURFACE hDestVSurface, hSrcVSurface; SGPRect SrcRect; GetVideoSurface( &hDestVSurface, guiRENDERBUFFER ); GetVideoSurface( &hSrcVSurface, guiCornerWhereTacticalIsStillSeenImage ); // Top SrcRect.iLeft = 0; SrcRect.iTop = 0; SrcRect.iRight = SCREEN_WIDTH; SrcRect.iBottom = SKI_TOTAL_BEGIN_Y; BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, SrcRect.iLeft, SrcRect.iTop, (RECT*)&SrcRect ); // Bottom SrcRect.iLeft = 0; SrcRect.iTop = SKI_TOTAL_END_Y; SrcRect.iRight = SCREEN_WIDTH; SrcRect.iBottom = SCREEN_HEIGHT - INV_INTERFACE_HEIGHT; BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, SrcRect.iLeft, SrcRect.iTop, (RECT*)&SrcRect ); // Left SrcRect.iLeft = 0; SrcRect.iTop = SKI_TOTAL_BEGIN_Y; SrcRect.iRight = SKI_TOTAL_BEGIN_X; SrcRect.iBottom = SKI_TOTAL_END_Y; BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, SrcRect.iLeft, SrcRect.iTop, (RECT*)&SrcRect ); // Right SrcRect.iLeft = SKI_TOTAL_END_X; SrcRect.iTop = SKI_TOTAL_BEGIN_Y; SrcRect.iRight = SCREEN_WIDTH; SrcRect.iBottom = SKI_TOTAL_END_Y; BltVSurfaceUsingDD( hDestVSurface, hSrcVSurface, VO_BLT_SRCTRANSPARENCY, SrcRect.iLeft, SrcRect.iTop, (RECT*)&SrcRect ); } void GetShopKeeperInterfaceUserInput() { InputAtom Event; POINT MousePos; GetCursorPos(&MousePos); ScreenToClient(ghWindow, &MousePos); // In window coords! while (DequeueSpecificEvent(&Event, KEY_DOWN|KEY_UP|KEY_REPEAT)) { if( !HandleTextInput( &Event ) && Event.usEvent == KEY_DOWN ) { switch( Event.usParam ) { case ESC: // clean exits - does quotes, shuts up shopkeeper, etc. ExitSKIRequested(); #ifdef JA2TESTVERSION //Instant exit - doesn't clean up much // gfSKIScreenExit = TRUE; #endif break; case 'x': if( Event.usKeyState & ALT_DOWN ) { HandleShortCutExitState( ); } break; case SPACE: { DeleteItemDescriptionBox( ); // skip Robot and EPCs SoldierID ubID = FindNextActiveAndAliveMerc( gpSMCurrentMerc, FALSE, TRUE ); gubSelectSMPanelToMerc = ubID; LocateSoldier( ubID, DONTSETLOCATOR ); // refresh background for player slots (in case item values change due to Flo's discount) gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } break; #ifdef JA2TESTVERSION case 'r': gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; break; case 'i': InvalidateRegion( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); break; case 'd': //Test key to toggle the display of the money each dealer has on hand gfTestDisplayDealerCash ^= 1; break; #endif } } } } #ifdef JA2TESTVERSION void DisplayAllDealersCash() { UINT16 usPosY=0; CHAR16 zTemp[512]; CHAR16 zTemp2[64]; UINT8 ubForeColor; //loop through all the shopkeeper's and display their money for ( INT8 bArmsDealer = 0; bArmsDealeruiFlags |= BUTTON_CLICKED_ON; InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { btn->uiFlags &= (~BUTTON_CLICKED_ON ); if( gSelectArmsDealerInfo.ubCurrentPage > 1 ) { gSelectArmsDealerInfo.ubCurrentPage--; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; EnableDisableDealersInventoryPageButtons(); } InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } } void BtnSKI_InvPageDownButtonCallback(GUI_BUTTON *btn,INT32 reason) { if(reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) { btn->uiFlags |= BUTTON_CLICKED_ON; InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { btn->uiFlags &= (~BUTTON_CLICKED_ON ); if( gSelectArmsDealerInfo.ubCurrentPage < gSelectArmsDealerInfo.ubNumberOfPages ) { gSelectArmsDealerInfo.ubCurrentPage++; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; EnableDisableDealersInventoryPageButtons(); } InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } } void BtnSKI_TransactionButtonCallback(GUI_BUTTON *btn,INT32 reason) { if(reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) { btn->uiFlags |= BUTTON_CLICKED_ON; InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { btn->uiFlags &= (~BUTTON_CLICKED_ON ); InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); // if not already doing it (multiple clicks can be queued up while shopkeeper is still talking) if ( !gfPerformTransactionInProgress ) { // shut him up ShutUpShopKeeper(); giShopKeepDialogueEventinProgress = -1; PerformTransaction( 0 ); } } } void BtnSKI_DoneButtonCallback(GUI_BUTTON *btn,INT32 reason) { if(reason & MSYS_CALLBACK_REASON_LBUTTON_DWN ) { btn->uiFlags |= BUTTON_CLICKED_ON; } if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { btn->uiFlags &= (~BUTTON_CLICKED_ON ); ExitSKIRequested(); } InvalidateRegion(btn->Area.RegionTopLeftX, btn->Area.RegionTopLeftY, btn->Area.RegionBottomRightX, btn->Area.RegionBottomRightY); } void CreateSkiInventorySlotMouseRegions( ) { int ubCnt, x,y; UINT16 usPosX, usPosY; // Create the mouse regions for the shopkeeper's inventory usPosY = SKI_TRADER_INVENTORY_BOXES_BEGIN_Y; ubCnt=0; for ( y = 0; y= gpTempDealersInventory.size() ) return; //if there are any items still there if( gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.exists() == true ) { //If the item type has not already been placed if( !( gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags & ARMS_INV_ITEM_SELECTED ) ) { //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { // ignore left clicks on items under repair. Fully repaired items are moved out to player's slots automatically } else // not a repairman { ubLocation = AddItemToArmsDealerOfferArea( &gpTempDealersInventory[ ubSelectedInvSlot ], ubSelectedInvSlot ); //if the item was added to the Dealer Offer Area correctly if( ubLocation != -1 ) { //Set the flag indicating the item has been selected gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags |= ARMS_INV_ITEM_SELECTED; //Specify the location the items went to gpTempDealersInventory[ ubSelectedInvSlot ].ubLocationOfObject = ARMS_DEALER_OFFER_AREA; gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation = ubLocation; //if the shift key is being pressed, remove them all if( gfKeyState[ SHIFT ] ) { DeleteObj(&gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject); } else { gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.RemoveObjectsFromStack(1); } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } } else // some of this item are already in dealer's offer area { UINT8 ubNumToMove; //if the shift key is being pressed, remove them all if( gfKeyState[ SHIFT ] ) { ubNumToMove = gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects; } else { ubNumToMove = 1; } //Reduce the number in dealer's inventory and Increase the number in dealer's offer area ArmsDealerOfferArea[ gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.ForceAddObjectsToStack( gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject, ubNumToMove); gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } } else if (iReason & MSYS_CALLBACK_REASON_RBUTTON_UP) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); if( gpTempDealersInventory.empty() == true ) return; ubSelectedInvSlot += gSelectArmsDealerInfo.ubFirstItemIndexOnPage; //if the selected slot is above any inventory we have, return if( ubSelectedInvSlot >= gpTempDealersInventory.size() ) return; if(gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.exists() == false) return; //DEF: //bring up the item description box if ( !InItemDescriptionBox( ) ) { InitShopKeeperItemDescBox( &gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject, ubSelectedInvSlot, ARMS_DEALER_INVENTORY ); } else { DeleteItemDescriptionBox( ); } //if the item has been seleceted if( gpTempDealersInventory[ ubSelectedInvSlot ].uiFlags & ARMS_INV_ITEM_SELECTED ) { //Check to see it there is more then 1 item in the location if( ArmsDealerOfferArea[ gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.exists() == true ) { //Decrease the number in the dealer offer area ArmsDealerOfferArea[ gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.MoveThisObjectTo(gTempObject, 1); //Increase the number in the dealer inventory gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject.AddObjectsToStack(gTempObject); //if there is nothing left in the arms dealer offer area if( ArmsDealerOfferArea[ gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.exists() == false ) { RemoveItemFromArmsDealerOfferArea( gpTempDealersInventory[ ubSelectedInvSlot ].bSlotIdInOtherLocation, FALSE ); } //redraw the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } } } void SelectDealersInventoryMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); ubSelectedInvSlot += gSelectArmsDealerInfo.ubFirstItemIndexOnPage; if( gpTempDealersInventory.empty() == true ) return; if( ubSelectedInvSlot >= gpTempDealersInventory.size() ) return; if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) { //if there is nothing in the slot, exit if( gpTempDealersInventory[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = &gpTempDealersInventory[ ubSelectedInvSlot ].ItemObject; HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE ); } else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) { //if there is nothing in the slot, exit if( gpTempDealersInventory[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = NULL; gubSkiDirtyLevel = SKI_DIRTY_LEVEL1; //ddd HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE ); } } void SelectDealersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) { //if there is nothing in the slot, exit if( ArmsDealerOfferArea[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = &ArmsDealerOfferArea[ ubSelectedInvSlot ].ItemObject; HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE ); } else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) { //if there is nothing in the slot, exit if( ArmsDealerOfferArea[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = NULL; gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;//ddd HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE ); } } void SelectPlayersOfferSlotsMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) { //if there is nothing in the slot, exit if( PlayersOfferArea[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = &PlayersOfferArea[ ubSelectedInvSlot ].ItemObject; HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, TRUE ); } else if(iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) { //if there is nothing in the slot, exit if( PlayersOfferArea[ ubSelectedInvSlot ].fActive == FALSE ) return; gpHighLightedItemObject = NULL; gubSkiDirtyLevel = SKI_DIRTY_LEVEL1;//ddd HandleCompatibleAmmoUI( gpSMCurrentMerc, -1, FALSE ); } } //Mouse Call back for the dealer's OFFER slot void SelectDealersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) { //if there is something here if( ArmsDealerOfferArea[ ubSelectedInvSlot ].fActive ) { //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { // return item to player RemoveRepairItemFromDealersOfferArea( ubSelectedInvSlot ); } else { //bring up the item description box if ( !InItemDescriptionBox( ) ) { InitShopKeeperItemDescBox( &ArmsDealerOfferArea[ ubSelectedInvSlot ].ItemObject, ubSelectedInvSlot, ARMS_DEALER_OFFER_AREA ); } else { DeleteItemDescriptionBox( ); } } } } else if( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) //MSYS_CALLBACK_REASON_LBUTTON_UP) { /* //if the current merc is disabled for whatever reason if( gfSMDisableForItems ) // the return; */ //if there is something here if( ArmsDealerOfferArea[ ubSelectedInvSlot ].fActive ) { //if this is a repair dealer if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { //if we don't have an item, pick one up if( gMoveingItem.sItemIndex == 0 ) { //if the dealer is a repair dealer, allow the player to pick up the item if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { BeginSkiItemPointer( ARMS_DEALER_OFFER_AREA, ubSelectedInvSlot, FALSE ); } } else { //swap what is in the cursor with what is in the player offer slot INVENTORY_IN_SLOT TempSlot; // if the slot is overloaded (holds more objects than we have room for valid statuses of) if ( ArmsDealerOfferArea[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT ) { // then ignore the click - we can't do the swap, or anything very useful, because we can't allow overloaded // items into the player's cursor - if he put them into a merc's inventory, the extra statuses are missing! // At best, we could do some sort of message here. return; } IfMercOwnedCopyItemToMercInv( &gMoveingItem ); TempSlot = ArmsDealerOfferArea[ ubSelectedInvSlot ]; ArmsDealerOfferArea[ ubSelectedInvSlot ] = gMoveingItem; gMoveingItem = TempSlot; IfMercOwnedRemoveItemFromMercInv( &gMoveingItem ); // Change mouse cursor gpItemPointer = &gMoveingItem.ItemObject; SetSkiCursor( EXTERN_CURSOR ); gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } else //non-repairman { //if the shift key is being pressed, remove them all if( gfKeyState[ SHIFT ] || ( ArmsDealerOfferArea[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects == 1 ) ) { RemoveItemFromArmsDealerOfferArea( ubSelectedInvSlot, TRUE );//ArmsDealerOfferArea[ ubSelectedInvSlot ].bSlotIdInOtherLocation } else // multiple items there, SHIFT isn't being pressed { // remove only one at a time gpTempDealersInventory[ ArmsDealerOfferArea[ ubSelectedInvSlot ].bSlotIdInOtherLocation ].ItemObject.ForceAddObjectsToStack(ArmsDealerOfferArea[ ubSelectedInvSlot ].ItemObject, 1); } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } else // empty slot { //if the cursor has something in it if( gMoveingItem.sItemIndex > 0 ) { // we'd better talking to a repairman, cursor is locked out of this area while full for non-repairmen! Assert( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ); //Drop the item into the current slot AddInventoryToSkiLocation( &gMoveingItem, ubSelectedInvSlot, ARMS_DEALER_OFFER_AREA ); //Reset the cursor SetSkiCursor( CURSOR_NORMAL ); //refresh the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } } } //Mouse Call back for the Players OFFER slot void SelectPlayersOfferSlotsRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { UINT16 ubSelectedInvSlot = MSYS_GetRegionUserData( pRegion, 0 ); INT16 bAddedToSlotID = -1; if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { //if the cursor has no item in it if( gMoveingItem.sItemIndex == 0 ) { //if there is nothing here, return if( PlayersOfferArea[ ubSelectedInvSlot ].fActive == FALSE ) return; // pick it up into the cursor BeginSkiItemPointer( PLAYERS_OFFER_AREA, ubSelectedInvSlot, FALSE ); } else // we have something in the cursor { //Drop the item into the current slot // AddObjectToSkiLocation( &gpSkiItemPointer, ubSelectedInvSlot, PLAYERS_OFFER_AREA ); //if there is something already there if( PlayersOfferArea[ ubSelectedInvSlot ].fActive ) { //swap what is in the cursor with what is in the player offer slot INVENTORY_IN_SLOT TempSlot; // if the slot is overloaded (holds more objects than we have room for valid statuses of) if ( PlayersOfferArea[ ubSelectedInvSlot ].ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT ) { // then ignore the click - we can't do the swap, or anything very useful, because we can't allow overloaded // items into the player's cursor - if he put them into a merc's inventory, the extra statuses are missing! // At best, we could do some sort of message here. return; } IfMercOwnedCopyItemToMercInv( &gMoveingItem ); TempSlot = PlayersOfferArea[ ubSelectedInvSlot ]; PlayersOfferArea[ ubSelectedInvSlot ] = gMoveingItem; gMoveingItem = TempSlot; IfMercOwnedRemoveItemFromMercInv( &gMoveingItem ); // Change mouse cursor gpItemPointer = &gMoveingItem.ItemObject; SetSkiCursor( EXTERN_CURSOR ); //if the item we are adding is money if( Item[ PlayersOfferArea[ ubSelectedInvSlot ].sItemIndex ].usItemClass == IC_MONEY && !( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) ) { //Since money is always evaluated PlayersOfferArea[ ubSelectedInvSlot ].uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE; PlayersOfferArea[ ubSelectedInvSlot ].uiItemPrice = PlayersOfferArea[ ubSelectedInvSlot ].ItemObject[0]->data.money.uiMoneyAmount; } } else // slot is empty { // if the item has already been evaluated, or has just been purchased if ( ( gMoveingItem.uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED ) || ( gMoveingItem.uiFlags & ARMS_INV_JUST_PURCHASED ) ) { //place the item that is in the player's hand into this player offer area slot bAddedToSlotID = AddInventoryToSkiLocation( &gMoveingItem, ubSelectedInvSlot, PLAYERS_OFFER_AREA ); Assert ( bAddedToSlotID != -1 ); } else { // this splits complex items for repairs. Also puts things into the first free POA slot OfferObjectToDealer( &gMoveingItem.ItemObject, gMoveingItem.ubIdOfMercWhoOwnsTheItem, gMoveingItem.bSlotIdInOtherLocation ); } //Reset the cursor SetSkiCursor( CURSOR_NORMAL ); } //Dirty the interface gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } else if(iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) //MSYS_CALLBACK_REASON_LBUTTON_UP) { //if the box is active if( PlayersOfferArea[ ubSelectedInvSlot ].fActive ) { RemoveItemFromPlayersOfferArea( ubSelectedInvSlot ); /* item description else { if ( !InItemDescriptionBox( ) ) { InitItemDescriptionBox( gpSMCurrentMerc, (UINT8)PlayersOfferArea[ ubSelectedInvSlot ].bSlotIdInOtherLocation, 214, 1 + INV_INTERFACE_START_Y, 0 ); } else { DeleteItemDescriptionBox( ); } } */ } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } void SelectSkiInventoryMovementAreaRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) { } } void EnterShopKeeperInterfaceScreen( UINT8 ubArmsDealer ) { gusIDOfCivTrader = NOBODY; //Get Dealer ID from from merc Id gbSelectedArmsDealerID = GetArmsDealerIDFromMercID( ubArmsDealer ); if( gbSelectedArmsDealerID <= -1 ) { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to find Arms Dealer ID From Merc ID #%d", ubArmsDealer ); #endif gfSKIScreenExit = TRUE; } LeaveTacticalScreen( SHOPKEEPER_SCREEN ); } // Flugente: set up shopkeeper with a non-NPC void EnterShopKeeperInterfaceScreen_NonNPC( INT8 ubArmsDealerID, SoldierID aMercID ) { gusIDOfCivTrader = aMercID; //Get Dealer ID from from merc Id gbSelectedArmsDealerID = ubArmsDealerID; if ( gbSelectedArmsDealerID <= -1 ) { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to find Arms Dealer ID From Merc ID #%d", ubArmsDealerID ); #endif gfSKIScreenExit = TRUE; } // Flugente: additional dialogue if ( gusSelectedSoldier != NOBODY ) AdditionalTacticalCharacterDialogue_CallsLua( gusSelectedSoldier, ADE_MERCHANT_CHAT, ubArmsDealerID >= 0 ? (UINT32)ubArmsDealerID : 0 ); LeaveTacticalScreen( SHOPKEEPER_SCREEN ); } void InitializeShopKeeper( BOOLEAN fResetPage ) { // update time player last dealt with him gArmsDealerStatus[ gbSelectedArmsDealerID ].uiTimePlayerLastInSKI = GetWorldTotalMin(); gpTempDealersInventory.clear(); HandlePossibleArmsDealerIntelRefresh(FALSE); //Get the number of distinct items in the inventory //Create the shopkeeper's temp inventory DetermineArmsDealersSellingInventory( ); gSelectArmsDealerInfo.ubNumberOfPages = (UINT8)(gpTempDealersInventory.size( ) / (SKI_TRADER_INVENTORY_BOXES_PER_ROW * SKI_TRADER_INVENTORY_BOXES_PER_COL)); if( gpTempDealersInventory.size() % 15 != 0 ) gSelectArmsDealerInfo.ubNumberOfPages += 1; //Should we reset the current inventory page being displayed if( gpTempDealersInventory.empty() == true ) gSelectArmsDealerInfo.ubCurrentPage = 0; else { if( fResetPage ) { if( gpTempDealersInventory.empty() == true ) gSelectArmsDealerInfo.ubCurrentPage = 0; else gSelectArmsDealerInfo.ubCurrentPage = 1; } //or if the current page will be an invalid page ( before the first, and after the last ) else if( gSelectArmsDealerInfo.ubCurrentPage < 0 || gSelectArmsDealerInfo.ubCurrentPage > gSelectArmsDealerInfo.ubNumberOfPages ) { gSelectArmsDealerInfo.ubCurrentPage = 1; } else if( gpTempDealersInventory.empty() == false ) gSelectArmsDealerInfo.ubCurrentPage = 1; } //if there is no inventory if( gpTempDealersInventory.empty() == true ) { gSelectArmsDealerInfo.ubCurrentPage = 0; gSelectArmsDealerInfo.ubNumberOfPages = 0; //disable the page up/down buttons DisableButton( guiSKI_InvPageUpButton ); DisableButton( guiSKI_InvPageDownButton ); return; } else { //enable the page up/down buttons EnableButton( guiSKI_InvPageUpButton ); EnableButton( guiSKI_InvPageDownButton ); } EnableDisableDealersInventoryPageButtons(); } void CalculateFirstItemIndexOnPage( ) { gSelectArmsDealerInfo.ubFirstItemIndexOnPage = (gSelectArmsDealerInfo.ubCurrentPage - 1) * (SKI_TRADER_INVENTORY_BOXES_PER_ROW * SKI_TRADER_INVENTORY_BOXES_PER_COL); } void DisplayArmsDealerCurrentInventoryPage( ) { CHAR16 zTemp[32]; UINT16 uiFontHeight; UINT16 usCnt=0; UINT16 usPosX, usPosY; int sItemCount=0; BOOLEAN fDisplayHatchOnItem=FALSE; usPosX = SKI_TRADER_INVENTORY_BOXES_BEGIN_X; usPosY = SKI_TRADER_INVENTORY_BOXES_BEGIN_Y; //if there is any inventory if( gpTempDealersInventory.empty() == false ) { if( gubSkiDirtyLevel != SKI_DIRTY_LEVEL0 ) { //Calculate the item that is at the top of the list CalculateFirstItemIndexOnPage( ); //Display the current inventory page DrawTextToScreen( SKI_Text[ SKI_TEXT_PAGE ], SKI_PAGE_X, (UINT16)(SKI_PAGE_Y+3), SKI_PAGE_WIDTH, SKI_LABEL_FONT, SKI_TITLE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); //Display the Current Page number uiFontHeight = GetFontHeight( SKI_LABEL_FONT ); swprintf( zTemp, L"%d/%d", gSelectArmsDealerInfo.ubCurrentPage, gSelectArmsDealerInfo.ubNumberOfPages ); DrawTextToScreen( zTemp, SKI_PAGE_X, (UINT16)(SKI_PAGE_Y+uiFontHeight+6), SKI_PAGE_WIDTH, SKI_LABEL_FONT, SKI_TITLE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); } //Display all the items for the current page for ( usCnt = gSelectArmsDealerInfo.ubFirstItemIndexOnPage; ((usCntubGraphicNum; pTrav = &(hVObject->pETRLEObject[ usGraphicNum ] ); usHeight = (UINT32)pTrav->usHeight; usWidth = (UINT32)pTrav->usWidth; sCenX = usPosX + 7 + (INT16)( abs( (long)(SKI_INV_WIDTH - 3 - usWidth) ) / 2 ) - pTrav->sOffsetX; sCenY = usPosY + (INT16)( abs( (long) (SKI_INV_HEIGHT - usHeight) ) / 2 ) - pTrav->sOffsetY; //blt the shadow of the item if(gGameSettings.fOptions[ TOPTION_SHOW_ITEM_SHADOW ]) BltVideoObjectOutlineShadowFromIndex( FRAME_BUFFER, GetInterfaceGraphicForItem( pItem ), g_bUsePngItemImages ? 0 : pItem->ubGraphicNum, sCenX-2, sCenY+2); //blt the item BltVideoObjectOutlineFromIndex( FRAME_BUFFER, GetInterfaceGraphicForItem( pItem ), g_bUsePngItemImages ? 0 : pItem->ubGraphicNum, sCenX, sCenY, Get16BPPColor( FROMRGB( 255, 255, 255 ) ), fHighlighted ); #ifdef ENCYCLOPEDIA_WORKS //Moa: encyclopedia item visibility EncyclopediaSetItemAsVisible( usItemIndex, ENC_ITEM_DISCOVERED_INSPECTABLE ); #endif //Display the status of the item //bfant: adjusted size of status bar for item text DrawItemUIBarEx( pItemObject, 0, (INT16)(usPosX+2), (INT16)(usPosY+2+24), 2, 24, Get16BPPColor( FROMRGB( 140, 136, 119 ) ), Get16BPPColor( FROMRGB( 140, 136, 119 ) ), TRUE, guiRENDERBUFFER );//guiSAVEBUFFER //Display the Items Cost if( ubItemArea == PLAYERS_OFFER_AREA ) { //if the item has value if( PlayersOfferArea[ ubSlotNum ].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE ) { uiItemCost = PlayersOfferArea[ ubSlotNum ].uiItemPrice; } //if the item belongs to a merc if( PlayersOfferArea[ ubSlotNum ].ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) { //Display the face of the merc fDisplayMercFace = TRUE; ubMercID = PlayersOfferArea[ ubSlotNum ].ubIdOfMercWhoOwnsTheItem; } // if the item has just been repaired if ( PlayersOfferArea[ ubSlotNum ].uiFlags & ARMS_INV_ITEM_REPAIRED ) { fPrintRepaired = TRUE; } } else if( ubItemArea == ARMS_DEALER_INVENTORY ) { if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { if( fHatchedOut && pItemObject->exists() == false ) { uiItemCost = 0; } else { // show the UNIT price, not the total value of all if stacked uiItemCost = CalcShopKeeperItemPrice( DEALER_SELLING, TRUE, usItemIndex, armsDealerInfo[ gbSelectedArmsDealerID ].dSellModifier, pItemObject ); } } else // UNDER REPAIR { //display the length of time needed to repair the item uiItemCost = 0; //Get the length of time needed to fix the item BuildDoneWhenTimeString( zTemp, gbSelectedArmsDealerID, &(gpTempDealersInventory[ ubSlotNum ]) ); DrawTextToScreen( zTemp, (UINT16)(usPosX+SKI_INV_PRICE_OFFSET_X), (UINT16)(usPosY+SKI_INV_PRICE_OFFSET_Y), SKI_INV_SLOT_WIDTH, SKI_ITEM_DESC_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); //if the item belongs to a merc if( gpTempDealersInventory[ ubSlotNum ].ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) { //Display the face of the merc fDisplayMercFace = TRUE; ubMercID = gpTempDealersInventory[ ubSlotNum ].ubIdOfMercWhoOwnsTheItem; } } } else // DEALER'S OFFER AREA { if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { //if the dealer repairs, there is an item here, therefore display the items owner's face fDisplayMercFace = TRUE; ubMercID = ArmsDealerOfferArea[ ubSlotNum ].ubIdOfMercWhoOwnsTheItem; uiItemCost = CalculateObjectItemRepairCost( gbSelectedArmsDealerID, pItemObject ); } else uiItemCost = CalcShopKeeperItemPrice( DEALER_SELLING, FALSE, usItemIndex, armsDealerInfo[ gbSelectedArmsDealerID ].dSellModifier, pItemObject ); } //if the item has a price, display it if( uiItemCost != 0 ) { swprintf( zTemp, L"%d", uiItemCost ); InsertCommasForDollarFigure( zTemp ); CHAR16 zTemp2[64]; if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) swprintf( zTemp2, L"%s Intel", zTemp ); else swprintf( zTemp2, L"$%s", zTemp ); DrawTextToScreen( zTemp2, (UINT16)(usPosX+SKI_INV_PRICE_OFFSET_X), (UINT16)(usPosY+SKI_INV_PRICE_OFFSET_Y+4), SKI_INV_SLOT_WIDTH, SKI_ITEM_DESC_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); } // bfant: Display item name, limited to displayable characters (space constraints) if ( pItem->szItemName != 0 ) { wcsncpy( zTemp, pItem->szItemName, sizeof( zTemp ) / 2 ); zTemp[63] = '\0'; // make sure it is null-terminated ReduceStringLength( zTemp, SKI_INV_SLOT_WIDTH - 2, BLOCKFONTNARROW ); DrawTextToScreen( zTemp, usPosX, usPosY + 1, SKI_INV_SLOT_WIDTH, BLOCKFONTNARROW, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); } //if the there is more then 1 or if the item is stackable and some of it has been bought and only 1 remains if( ( pItemObject->ubNumberOfObjects > 1 ) || ( ( pItemObject->ubNumberOfObjects == 1 ) && ( ubItemArea == ARMS_DEALER_INVENTORY ) && ( gpTempDealersInventory[ ubSlotNum ].uiFlags & ARMS_INV_ITEM_SELECTED ) ) ) { swprintf( zTemp, L"x%d", pItemObject->ubNumberOfObjects ); DrawTextToScreen( zTemp, (UINT16)(usPosX+SKI_ITEM_NUMBER_TEXT_OFFSET_X), (UINT16)(usPosY+SKI_ITEM_NUMBER_TEXT_OFFSET_Y), SKI_ITEM_NUMBER_TEXT_WIDTH, SKIT_NUMBER_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, FALSE, RIGHT_JUSTIFIED ); } //if we are to display the face if( fDisplayMercFace ) { INT8 iFaceSlot = GetSlotNumberForMerc( ubMercID ); // if still in player's employ if ( iFaceSlot != -1 ) { //Get and blit the face GetVideoObject(&hPixHandle, guiSmallSoldiersFace[ iFaceSlot ] ); BltVideoObject(FRAME_BUFFER, hPixHandle, 0,(UINT16)(usPosX+SKI_SMALL_FACE_OFFSET_X), (UINT16)(usPosY), VO_BLT_SRCTRANSPARENCY,NULL);//SKI_SMALL_FACE_OFFSET_Y } } //if the item has attachments if( ItemHasAttachments( pItemObject ) ) { //Display the '*' in the bottom right corner of the square swprintf( zTemp, L"*" ); DrawTextToScreen( zTemp, (UINT16)(usPosX+SKI_ATTACHMENT_SYMBOL_X_OFFSET), (UINT16)(usPosY+SKI_ATTACHMENT_SYMBOL_Y_OFFSET), 0, TINYFONT1, FONT_GREEN, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); } // CHRISL: if item is LBENODE else if( (UsingNewInventorySystem() == true) && pItemObject->HasAnyActiveLBEs() == true) { //Display the '*' in the bottom right corner of the square swprintf( zTemp, L"*" ); DrawTextToScreen( zTemp, (UINT16)(usPosX+SKI_ATTACHMENT_SYMBOL_X_OFFSET), (UINT16)(usPosY+SKI_ATTACHMENT_SYMBOL_Y_OFFSET), 0, TINYFONT1, FONT_BLUE, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); } // Display 'JAMMED' if it's jammed if ( (*pItemObject)[0]->data.gun.bGunAmmoStatus < 0 ) { swprintf( zTemp, TacticalStr[ JAMMED_ITEM_STR ] ); VarFindFontCenterCoordinates( usPosX, usPosY, SKI_INV_SLOT_WIDTH, SKI_INV_HEIGHT, TINYFONT1, &sCenX, &sCenY, zTemp ); DrawTextToScreen( zTemp, sCenX, sCenY, SKI_INV_SLOT_WIDTH, TINYFONT1, FONT_RED, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); } else if ( fPrintRepaired ) { swprintf( zTemp, SKI_Text[ SKI_TEXT_REPAIRED ] ); VarFindFontCenterCoordinates( usPosX, usPosY, SKI_INV_SLOT_WIDTH, SKI_INV_HEIGHT, TINYFONT1, &sCenX, &sCenY, zTemp ); DrawTextToScreen( zTemp, sCenX, sCenY, SKI_INV_SLOT_WIDTH, TINYFONT1, FONT_RED, FONT_MCOLOR_BLACK, FALSE, LEFT_JUSTIFIED ); } if( fHatchedOut ) { HatchOutInvSlot( usPosX, usPosY ); } InvalidateRegion( usPosX, usPosY, usPosX+SKI_INV_SLOT_WIDTH, usPosY+SKI_INV_SLOT_HEIGHT ); return( uiItemCost ); } BOOLEAN DetermineArmsDealersSellingInventory( ) { DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("DEF: DetermineArmsDealersSellingInventory") ); //allocate memory to hold the inventory in memory gpTempDealersInventory.clear(); guiNextFreeInvSlot = 0; //loop through the dealer's permanent inventory items, adding them all to the temp inventory list for (DealerItemList::iterator iter = gArmsDealersInventory[ gbSelectedArmsDealerID ].begin(); iter != gArmsDealersInventory[ gbSelectedArmsDealerID ].end();) { if ( iter->ItemIsInInventory() == false) { ++iter; continue; } bool increment = true; if (ItemIsSpecial(*iter) == false) { StoreObjectsInNextFreeDealerInvSlot( &(*iter), gpTempDealersInventory, gbSelectedArmsDealerID ); } else { //if the object has attachments, or is damaged, or otherwise special, then it is not stacked bool fAddSpecialItem = true; bool underRepair = false; bool repaired = false; if( iter->IsUnderRepair() == true ) { underRepair = true; //if the repairs are done if( iter->uiRepairDoneTime <= GetWorldTotalMin() ) { if (RepairIsDone( &(*iter) )) { //ADB moved from RepairIsDone so iterator remains ok // Remove the repaired item from the dealer's permanent inventory list iter = gArmsDealersInventory[ gbSelectedArmsDealerID ].erase(iter); if (iter == gArmsDealersInventory[ gbSelectedArmsDealerID ].end()) { break; } increment = false; // don't add it here, it was put in the player's area fAddSpecialItem = false; } else { repaired = true; } } } if ( fAddSpecialItem == true ) { UINT8 ubOwner; if ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { // no merc is the owner ubOwner = NO_PROFILE; } else { // retain owner so we can display this ubOwner = iter->ubOwnerProfileId; } StoreObjectsInNextFreeDealerInvSlot( &(*iter), gpTempDealersInventory, ubOwner ); if (underRepair == true) { if (repaired == true) { gpTempDealersInventory.back().uiFlags |= ARMS_INV_ITEM_REPAIRED; } else { gpTempDealersInventory.back().uiFlags |= ARMS_INV_ITEM_NOT_REPAIRED_YET; } } } } if (increment == true) { ++iter; } } // repairmen sort differently from merchants // sort this list by object category, and by ascending price within each category if ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { std::sort(gpTempDealersInventory.begin(), gpTempDealersInventory.end(), RepairmanItemQsortCompare);//RepairmanItemQsortCompare } else { std::sort(gpTempDealersInventory.begin(), gpTempDealersInventory.end(), ArmsDealerItemQsortCompare);//ArmsDealerItemQsortCompare } return( TRUE ); } bool ArmsDealerItemQsortCompare(INVENTORY_IN_SLOT& pInvSlot1, INVENTORY_IN_SLOT& pInvSlot2) { int retVal = ( CompareItemsForSorting( pInvSlot1.sItemIndex, pInvSlot2.sItemIndex, pInvSlot1.ItemObject[0]->data.objectStatus, pInvSlot2.ItemObject[0]->data.objectStatus ) ); if (retVal == -1) { return true; } return false; } bool RepairmanItemQsortCompare(INVENTORY_IN_SLOT& pInvSlot1, INVENTORY_IN_SLOT& pInvSlot2) { // lower repair time first if ( pInvSlot1.uiRepairDoneTime < pInvSlot2.uiRepairDoneTime ) { return true; } return false; } void StoreObjectsInNextFreeDealerInvSlot( DEALER_SPECIAL_ITEM *pSpclItemInfo, std::vector& pInventory, UINT8 ubOwner ) { pInventory.push_back(INVENTORY_IN_SLOT()); INVENTORY_IN_SLOT* pDealerInvSlot = &(pInventory.back()); pDealerInvSlot->fActive = TRUE; pDealerInvSlot->sItemIndex = pSpclItemInfo->object.usItem; pDealerInvSlot->uiRepairDoneTime = pSpclItemInfo->uiRepairDoneTime; pDealerInvSlot->ubIdOfMercWhoOwnsTheItem = ubOwner; pDealerInvSlot->bSlotIdInOtherLocation = -1; // Create the item object ( with no more than MAX_OBJECTS_PER_SLOT ) // can't use the real #, because CreateItems() will blindly set the bStatus for however many we tell it, beyond 8 MakeObjectOutOfDealerItems( pSpclItemInfo, &(pDealerInvSlot->ItemObject) ); //ADB no longer necessary /* if ( ubHowMany > MAX_OBJECTS_PER_SLOT ) { // HACK: Override ItemObject->ubNumberOfObjects (1-8) with the REAL # of items in this box. // Note that this makes it an illegal OBJECTTYPE, since there ubHowMany can easily be more than MAX_OBJECTS_PER_SLOT, // so there's no room to store the status of all of them one. But we only so this for perfect items, so // we don't care, it works & saves us a lot hassles here. Just be careful using the damn things!!! You can't just // pass them off the most functions in Items.C(), use ShopkeeperAutoPlaceObject() and ShopkeeperAddItemToPool() instead. pDealerInvSlot->ItemObject.ubNumberOfObjects = ubHowMany; } */ } BOOLEAN RepairIsDone(DEALER_SPECIAL_ITEM* pSpecial) { // make a new shopkeeper invslot item out of it INVENTORY_IN_SLOT RepairItem; RepairItem.fActive = TRUE; RepairItem.sItemIndex = pSpecial->object.usItem; // set the owner of the item. Slot is always -1 of course. RepairItem.ubIdOfMercWhoOwnsTheItem = pSpecial->ubOwnerProfileId; RepairItem.bSlotIdInOtherLocation = -1; // Create the item object MakeObjectOutOfDealerItems( pSpecial, &RepairItem.ItemObject ); if ( CanDealerRepairItem( gbSelectedArmsDealerID, RepairItem.ItemObject.usItem ) ) { // make its condition 100% RepairItem.ItemObject[0]->data.objectStatus = 100; // Flugente: shopkeepers will also restore an item's repair threshold RepairItem.ItemObject[0]->data.sRepairThreshold = 100; } // max condition of all permanent attachments on it for (attachmentList::iterator iter = RepairItem.ItemObject[0]->attachments.begin(); iter != RepairItem.ItemObject[0]->attachments.end(); ++iter) { if ( CanDealerRepairItem( gbSelectedArmsDealerID, iter->usItem ) && iter->exists()) { // fix it up (*iter)[0]->data.objectStatus = 100; // Flugente: shopkeepers will also restore an item's repair threshold and clean it (*iter)[0]->data.sRepairThreshold = 100; } } #ifdef JA2UB //no UB #else // if the item is imprinted (by anyone, even player's mercs), and it's Fredo repairing it if ( ( gbSelectedArmsDealerID == ARMS_DEALER_FREDO ) ) { // then reset the imprinting! RepairItem.ItemObject[0]->data.ubImprintID = NO_PROFILE; } #endif //try to add the item to the players offer area INT8 bSlotNum; bSlotNum = AddItemToPlayersOfferArea( RepairItem.ubIdOfMercWhoOwnsTheItem, &RepairItem, -1 ); // if there wasn't room for it if( bSlotNum == -1 ) { // then we have to treat it like it's not done yet! return(FALSE); } PlayersOfferArea[ bSlotNum ].uiFlags |= ARMS_INV_ITEM_REPAIRED; // there was room to add it to player's area return(TRUE); } void DrawHatchOnInventory( UINT32 uiSurface, UINT16 usPosX, UINT16 usPosY, UINT16 usWidth, UINT16 usHeight, UINT16 usColor ) { UINT8 *pDestBuf; UINT32 uiDestPitchBYTES; SGPRect ClipRect; static UINT8 Pattern[8][8] = { 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1, 1,0,1,0,1,0,1,0, 0,1,0,1,0,1,0,1 }; // CHRISL: ClipRect.iLeft = usPosX-1; ClipRect.iRight = usPosX + usWidth-1; ClipRect.iTop = usPosY; ClipRect.iBottom = usPosY + usHeight; pDestBuf = LockVideoSurface( uiSurface, &uiDestPitchBYTES ); if(usColor == 0){ Blt16BPPBufferPixelateRect( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, Pattern ); } else{ Blt16BPPBufferPixelateRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, Pattern, usColor ); } UnLockVideoSurface( uiSurface ); } void DrawHatchOnInventory_MilitiaAccess( UINT32 uiSurface, UINT16 usPosX, UINT16 usPosY, UINT16 usWidth, UINT16 usHeight, UINT16 usColor ) { UINT8 *pDestBuf; UINT32 uiDestPitchBYTES; SGPRect ClipRect; static UINT8 Pattern[8][8] = { 1,0,0,0,0,0,0,0, 0,0,0,0,0,1,0,0, 0,0,1,0,0,0,0,0, 0,0,0,0,0,0,0,1, 0,0,0,0,1,0,0,0, 0,1,0,0,0,0,0,0, 0,0,0,0,0,0,1,0, 0,0,0,1,0,0,0,0 }; // CHRISL: ClipRect.iLeft = usPosX-1; ClipRect.iRight = usPosX + usWidth-1; ClipRect.iTop = usPosY; ClipRect.iBottom = usPosY + usHeight; pDestBuf = LockVideoSurface( uiSurface, &uiDestPitchBYTES ); if(usColor == 0){ Blt16BPPBufferPixelateRect( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, Pattern ); } else{ Blt16BPPBufferPixelateRectWithColor( (UINT16*)pDestBuf, uiDestPitchBYTES, &ClipRect, Pattern, usColor ); } UnLockVideoSurface( uiSurface ); } UINT32 CalcShopKeeperItemPrice( BOOLEAN fDealerSelling, BOOLEAN fUnitPriceOnly, UINT16 usItemID, FLOAT dModifier, OBJECTTYPE *pItemObject ) { UINT8 ubCnt; UINT32 uiUnitPrice = 0; UINT32 uiTotalPrice = 0; // UINT32 uiDifFrom10 = 0; #ifdef JA2UB UINT8 ubItemsNotCounted = 0; //ja25 UB #endif std::vector uiItemPrice; uiItemPrice.resize(pItemObject->ubNumberOfObjects); for (ubCnt = 0; ubCnt < pItemObject->ubNumberOfObjects; ++ubCnt ) { uiItemPrice[ubCnt] = 0; // add up value of the main item(s), exact procedure depends on its item class switch ( Item [ usItemID ].usItemClass ) { case IC_GUN: // add value of the gun uiItemPrice[ubCnt] += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, usItemID, fDealerSelling ) * ItemConditionModifier(usItemID, (*pItemObject)[ubCnt]->data.gun.bGunStatus) * dModifier ); // if any ammo is loaded if( (*pItemObject)[ubCnt]->data.gun.usGunAmmoItem != NONE) { // if it's regular ammo if( Item[ (*pItemObject)[ubCnt]->data.gun.usGunAmmoItem ].usItemClass == IC_AMMO ) { // add value of its remaining ammo uiItemPrice[ubCnt] += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, (*pItemObject)[ubCnt]->data.gun.usGunAmmoItem, fDealerSelling ) * ItemConditionModifier((*pItemObject)[ubCnt]->data.gun.usGunAmmoItem, (*pItemObject)[ubCnt]->data.gun.ubGunShotsLeft) * dModifier ); } else // assume it's attached ammo (mortar shells, grenades) { // add its value (uses normal status 0-100) uiItemPrice[ubCnt] += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, (*pItemObject)[ubCnt]->data.gun.usGunAmmoItem, fDealerSelling ) * ItemConditionModifier((*pItemObject)[ubCnt]->data.gun.usGunAmmoItem, (*pItemObject)[ubCnt]->data.gun.bGunAmmoStatus) * dModifier ); } } break; case IC_AMMO: // add the value of each magazine (multiple mags may have vastly different #bullets left) // for bullets, ItemConditionModifier will convert the #ShotsLeft into a percentage uiItemPrice[ubCnt] += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, usItemID, fDealerSelling ) * ItemConditionModifier(usItemID, (*pItemObject)[ubCnt]->data.ubShotsLeft) * dModifier ); break; default: // add the value of each magazine (multiple mags may have vastly different #bullets left) // for bullets, ItemConditionModifier will convert the #ShotsLeft into a percentage uiItemPrice[ubCnt] += (UINT32)( CalcValueOfItemToDealer( gbSelectedArmsDealerID, usItemID, fDealerSelling ) * ItemConditionModifier(usItemID, (*pItemObject)[ ubCnt ]->data.objectStatus) * dModifier ); break; } // loop through any attachments and add in their price for (attachmentList::iterator iter = (*pItemObject)[ubCnt]->attachments.begin(); iter != (*pItemObject)[ubCnt]->attachments.end(); ++iter) { // add value of this particular attachment if(iter->exists()) uiItemPrice[ubCnt] += CalcShopKeeperItemPrice( fDealerSelling, fUnitPriceOnly, iter->usItem, dModifier, &(*iter)) ; } // Flugente: used in backgrounds now, old code in comments for historical reasons /*// if Flo is doing the dealin' and wheelin' if ( gpSMCurrentMerc->ubProfile == FLO ) { // if it's a GUN or AMMO (but not Launchers, and all attachments and payload is included) switch ( Item [ usItemID ].usItemClass ) { case IC_GUN: case IC_AMMO: uiDiscountValue = ( uiItemPrice[ubCnt] * FLO_DISCOUNT_PERCENTAGE ) / 100; // she gets a discount! Read her M.E.R.C. profile to understand why if ( fDealerSelling ) { // she buys for less... uiItemPrice[ubCnt] -= uiDiscountValue; } else { // and sells for more! uiItemPrice[ubCnt] += uiDiscountValue; } break; } }*/ // Flugente: backgrounds can alter prices INT16 pricepercentage = 100; // if it's a GUN or AMMO (but not Launchers, and all attachments and payload is included) if ( Item [ usItemID ].usItemClass & (IC_GUN|IC_AMMO) ) { pricepercentage = max( 0, 100 + (fDealerSelling ? -1 : 1) * gpSMCurrentMerc->GetBackgroundValue( BG_PERC_PRICES_GUNS ) ); // Even without backgrounds, Flo gets a discount. Read her M.E.R.C. profile to understand why if ( !UsingBackGroundSystem() && gpSMCurrentMerc->ubProfile == FLO ) pricepercentage = max( 0, 100 + (fDealerSelling ? -1 : 1) * FLO_DISCOUNT_PERCENTAGE ); } else if ( Item [ usItemID ].usItemClass & (IC_MAPFILTER_MELEE|IC_MAPFILTER_KIT|IC_MAPFILTER_LBE|IC_MAPFILTER_ARMOR|IC_MAPFILTER_MISC) ) { pricepercentage = max( 0, 100 + (fDealerSelling ? -1 : 1) * gpSMCurrentMerc->GetBackgroundValue( BG_PERC_PRICES ) ); } uiItemPrice[ubCnt] = (uiItemPrice[ubCnt] * pricepercentage) / 100; // if it's the dealer selling this, make sure the item is worth at least $1 // if he is buying this from a player, then we allow a value of 0, since that has a special "worthless" quote #18 if( fDealerSelling && ( uiItemPrice[ubCnt] == 0 ) ) { uiItemPrice[ubCnt] = 1; } /* //if the price is not diviseble by 10, make it so uiDifFrom10 = 10 - uiItemPrice[ubCnt] % 10; if( uiDifFrom10 != 0 && uiDifFrom10 != 10 ) { uiItemPrice[ubCnt] += uiDifFrom10; } */ } // if we want the unit price, find the highest price in the stack for (ubCnt = 0; ubCnt < pItemObject->ubNumberOfObjects; ++ubCnt ) { if(fUnitPriceOnly == TRUE) { uiUnitPrice = __max(uiUnitPrice, uiItemPrice[ubCnt]); } else { uiUnitPrice += uiItemPrice[ubCnt]; } } // we're always count the first one uiTotalPrice = uiUnitPrice; #ifdef JA2UB //---------------------------JA25 UB--------------- // if NOT pricing just one if ( !fUnitPriceOnly ) { // add value of all that weren't already counted uiTotalPrice += ( ubItemsNotCounted * uiUnitPrice ); } //if the dealer is Raul if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //if the item is the antique musket if( usItemID == HAND_CANNON ) //4498 { //if the "Find anitque musket" quest is active if( gubQuest[ QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL ] == QUESTINPROGRESS ) { //the price should be.... nothin ( reward for the quest ) uiTotalPrice = 1; //if the player hasnt said the quote before if( !( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_48 ) ) { StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_48 ); gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_48; gfCanSayMakeTransactionQuote = FALSE; } //else if if( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_49 ) { //if the player hasnt said it before if( !( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_50 ) ) { StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_50 ); gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_50; } //the price should be uiTotalPrice = 100; } } } //if the item is the barrat if( usItemID == BARRETT_UB ) { //and if the player found the antique musket for Raul if( IsJa25GeneralFlagSet( JA_GF__BARRETT_IS_HALF_PRICE ) ) { //the barrett is half off uiTotalPrice /= 2; } } } //JA25UB //if the dealer is Raul if( gbSelectedArmsDealerID == ARMS_DEALER_BETTY ) { //if the item is the antique musket if( usItemID == TEX_MOVIE_ATTACK_CLYDESDALES || usItemID == TEX_MOVIE_WILD_EAST || usItemID == TEX_MOVIE_HAVE_HONDA ) // if( usItemID == 1356 || usItemID == 1357 || usItemID == 1358 ) { //Make the item price top be what is listed in the item table ( 20 ) uiTotalPrice = Item[ usItemID ].usPrice; } } #endif return( uiTotalPrice ); } FLOAT ItemConditionModifier(UINT16 usItemIndex, INT16 bStatus) { FLOAT dConditionModifier = 1.0f; //if the item is ammo, the condition modifier is based on how many shots are left if( Item[ usItemIndex ].usItemClass == IC_AMMO ) { // # bullets left / max magazine capacity dConditionModifier = ( bStatus / (FLOAT) Magazine[ Item[ usItemIndex ].ubClassIndex ].ubMagSize ); } else // non-ammo { // handle non-ammo payloads in jammed guns as if they weren't if ( bStatus < 0 ) { bStatus *= -1; } // an item at 100% is worth full price... if (ItemIsRepairable(usItemIndex)) { // a REPAIRABLE item at 0% is still worth 50% of its full price, not 0% dConditionModifier = 0.5f + ( bStatus / (FLOAT)200 ); } else { // an UNREPAIRABLE item is worth precisely its condition percentage dConditionModifier = bStatus / (FLOAT)100; } } return( dConditionModifier ); } void DisplayArmsDealerOfferArea() { INT16 sCnt, sCount; CHAR16 zTemp[32]; UINT32 uiTotalCost; UINT16 usPosX, usPosY; BOOLEAN fDisplayHatchOnItem; usPosX = SKI_TRADER_OFFER_BOXES_BEGIN_X; usPosY = SKI_TRADER_OFFER_BOXES_BEGIN_Y; sCount = 0; //Display all the items that are in the Arms traders offered area uiTotalCost = 0; for ( sCnt = 0; sCntItemObject.ubNumberOfObjects > 1 ) { ArmsDealerOfferArea[bCnt].ItemObject.RemoveObjectsFromStack(ArmsDealerOfferArea[bCnt].ItemObject.ubNumberOfObjects - 1); } //Remember where the item came from ArmsDealerOfferArea[bCnt].bSlotIdInOtherLocation = bSlotIdInOtherLocation; ArmsDealerOfferArea[bCnt].ubIdOfMercWhoOwnsTheItem = pInvSlot->ubIdOfMercWhoOwnsTheItem; ArmsDealerOfferArea[bCnt].fActive = TRUE; SetSkiRegionHelpText( &ArmsDealerOfferArea[bCnt], &gDealersOfferSlotsMouseRegions[ bCnt ], ARMS_DEALER_OFFER_AREA ); SetSkiFaceRegionHelpText( &ArmsDealerOfferArea[bCnt], &gDealersOfferSlotsSmallFaceMouseRegions[ bCnt ], ARMS_DEALER_OFFER_AREA ); gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; return( bCnt ); } } return( -1 ); } BOOLEAN RemoveItemFromArmsDealerOfferArea( INT16 bSlotId, BOOLEAN fKeepItem ) { //if this is the requested slot if( ArmsDealerOfferArea[bSlotId].fActive ) { if( fKeepItem ) { gpTempDealersInventory[ ArmsDealerOfferArea[bSlotId].bSlotIdInOtherLocation ].ItemObject.ForceAddObjectsToStack(ArmsDealerOfferArea[ bSlotId ].ItemObject); } //Clear the flag that hatches out the item gpTempDealersInventory[ ArmsDealerOfferArea[bSlotId].bSlotIdInOtherLocation ].uiFlags &= ~ARMS_INV_ITEM_SELECTED; ClearArmsDealerOfferSlot( bSlotId ); return( TRUE ); } return( FALSE ); } void SetSkiRegionHelpText( INVENTORY_IN_SLOT *pInv, MOUSE_REGION *pRegion, UINT8 ubScreenArea ) { CHAR16 zHelpText[ 512 ]; Assert( pRegion ); BuildItemHelpTextString( zHelpText, pInv, ubScreenArea ); SetRegionFastHelpText( pRegion, zHelpText ); SetRegionHelpEndCallback( pRegion, SkiHelpTextDoneCallBack ); } void SetSkiFaceRegionHelpText( INVENTORY_IN_SLOT *pInv, MOUSE_REGION *pRegion, UINT8 ubScreenArea ) { CHAR16 zTempText[ 512 ]; CHAR16 zHelpText[ 512 ]; Assert( pRegion ); //if the item isn't NULL, and is owned by a merc if( ( pInv != NULL ) && ( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) ) { BuildItemHelpTextString( zTempText, pInv, ubScreenArea ); // add who owns it swprintf( zHelpText, L"%s%s %s", gMercProfiles[ pInv->ubIdOfMercWhoOwnsTheItem ].zNickname, pMessageStrings[ MSG_DASH_S ], zTempText ); } else { zHelpText[ 0 ] = '\0'; } SetRegionFastHelpText( pRegion, zHelpText ); SetRegionHelpEndCallback( pRegion, SkiHelpTextDoneCallBack ); } void SkiHelpTextDoneCallBack( void ) { gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } INT8 AddItemToPlayersOfferArea( UINT8 ubProfileID, INVENTORY_IN_SLOT* pInvSlot, INT16 bSlotIdInOtherLocation ) { //if we are to check for a previous slot if( bSlotIdInOtherLocation != -1 ) { //if the item has already been added somewhere. don't add it again if( GetPtrToOfferSlotWhereThisItemIs( ubProfileID, bSlotIdInOtherLocation ) != NULL ) return( -1 ); } //look for the first free slot for ( int bCnt = 0; bCntdata.money.uiMoneyAmount; } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; return( bCnt ); } } return( -1 ); } BOOLEAN RemoveItemFromPlayersOfferArea( INT16 bSlot ) { //if the item doesn't have a duplicate copy in its owner merc's inventory slot if( PlayersOfferArea[ bSlot ].bSlotIdInOtherLocation == -1 ) { if ( !SKITryToReturnInvToOwnerOrCurrentMerc( &( PlayersOfferArea[ bSlot ] ) ) ) { //failed to add item, inventory probably filled up or item is unowned and current merc ineligible return( FALSE ); } } // Clear the contents ClearPlayersOfferSlot( bSlot ); // Dirty fInterfacePanelDirty = DIRTYLEVEL2; gubSkiDirtyLevel = SKI_DIRTY_LEVEL1; return( TRUE ); } void DisplayPlayersOfferArea() { INT16 sCount = 0; CHAR16 zTemp[32]; UINT32 uiTotalCost; UINT16 usPosX, usPosY; BOOLEAN fDisplayHatchOnItem=FALSE; HVOBJECT hHandle; // load the "cross out" graphic GetVideoObject( &hHandle, guiItemCrossOut ); usPosX = SKI_PLAYER_OFFER_BOXES_BEGIN_X; usPosY = SKI_PLAYER_OFFER_BOXES_BEGIN_Y; //Display all the items that are in the Players offered area uiTotalCost = 0; for ( int sCnt = 0; sCnt < gPlayersOfferActiveRegions; ++sCnt ) { if( PlayersOfferArea[ sCnt ].fActive ) { //if the item is money if( Item[ PlayersOfferArea[ sCnt ].sItemIndex ].usItemClass == IC_MONEY ) { //get an updated status from the amount in the pocket if( PlayersOfferArea[ sCnt ].bSlotIdInOtherLocation != -1 && PlayersOfferArea[ sCnt ].ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) { SoldierID sSoldierID = GetSoldierIDFromMercID( PlayersOfferArea[ sCnt ].ubIdOfMercWhoOwnsTheItem ); Assert(sSoldierID != NOBODY); PlayersOfferArea[ sCnt ].ItemObject[0]->data.money.uiMoneyAmount = sSoldierID->inv[ PlayersOfferArea[ sCnt ].bSlotIdInOtherLocation ][0]->data.money.uiMoneyAmount; PlayersOfferArea[ sCnt ].uiItemPrice = PlayersOfferArea[ sCnt ].ItemObject[0]->data.money.uiMoneyAmount; } } else // not money { //if non-repairman if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { // don't evaluate anything he wouldn't buy! if( WillShopKeeperRejectObjectsFromPlayer( gbSelectedArmsDealerID, ( INT8 ) sCnt ) == FALSE ) { // skip purchased items! if ( !( PlayersOfferArea[ sCnt ].uiFlags & ARMS_INV_JUST_PURCHASED ) ) { // re-evaluate the value of the item (needed for Flo's discount handling) EvaluateInvSlot( &( PlayersOfferArea[ sCnt ] ) ); } } } } // Display the inventory slot DisplayInvSlot( (UINT16)sCnt, PlayersOfferArea[ sCnt ].sItemIndex, usPosX, usPosY, &PlayersOfferArea[ sCnt ].ItemObject, fDisplayHatchOnItem, PLAYERS_OFFER_AREA ); if( PlayersOfferArea[ sCnt ].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE ) uiTotalCost += PlayersOfferArea[ sCnt ].uiItemPrice; // cross out unwanted items if ( !(PlayersOfferArea[sCnt].uiFlags & ARMS_INV_JUST_PURCHASED) ) { //If item can't be sold here, or it's completely worthless (very cheap / very broken) if ( (WillShopKeeperRejectObjectsFromPlayer( gbSelectedArmsDealerID, sCnt ) == TRUE) || !(PlayersOfferArea[sCnt].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE) ) { BltVideoObject( FRAME_BUFFER, hHandle, 0, (usPosX + 22), (usPosY), VO_BLT_SRCTRANSPARENCY, NULL ); // invalidate the region InvalidateRegion( usPosX - 1, usPosY - 1, usPosX + SKI_INV_SLOT_WIDTH + 1, usPosY + SKI_INV_SLOT_HEIGHT + 1 ); } } } usPosX += SKI_INV_OFFSET_X; ++sCount; //if we are on to the next row if ( !(sCount % SKI_TRADER_OFFER_BOXES_PER_ROW) ) { usPosX = SKI_PLAYER_OFFER_BOXES_BEGIN_X; usPosY += SKI_INV_OFFSET_Y; } } if( gubSkiDirtyLevel == SKI_DIRTY_LEVEL2 ) { //Display the total cost text swprintf( zTemp, L"%d", uiTotalCost ); InsertCommasForDollarFigure( zTemp ); CHAR16 zTemp2[64]; if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) swprintf( zTemp2, L"%s Intel", zTemp ); else swprintf( zTemp2, L"$%s", zTemp ); DrawTextToScreen( zTemp2, SKI_TOTAL_VALUE_X, SKI_TOTAL_VALUE_OFFSET_TO_VALUE, SKI_INV_SLOT_WIDTH, SKI_LABEL_FONT, SKI_ITEM_PRICE_COLOR, FONT_MCOLOR_BLACK, FALSE, CENTER_JUSTIFIED ); } } INVENTORY_IN_SLOT *GetPtrToOfferSlotWhereThisItemIs( UINT8 ubProfileID, INT16 bInvPocket ) { for ( int ubCnt = 0; ubCnt < gPlayersOfferActiveRegions; ++ubCnt ) { if( ( PlayersOfferArea[ ubCnt ].bSlotIdInOtherLocation == bInvPocket ) && ( PlayersOfferArea[ ubCnt ].ubIdOfMercWhoOwnsTheItem == ubProfileID ) && ( PlayersOfferArea[ ubCnt ].ItemObject.exists() == true ) ) { return( &( PlayersOfferArea[ ubCnt ] ) ); } } for ( int ubCnt = 0; ubCnt < gDealersOfferActiveRegions; ++ubCnt ) { if ( (ArmsDealerOfferArea[ubCnt].bSlotIdInOtherLocation == bInvPocket) && (ArmsDealerOfferArea[ubCnt].ubIdOfMercWhoOwnsTheItem == ubProfileID) && (ArmsDealerOfferArea[ubCnt].ItemObject.exists( ) == true) ) { return(&(ArmsDealerOfferArea[ubCnt])); } } // not found (perfectly valid result) return( NULL ); } BOOLEAN ShouldSoldierDisplayHatchOnItem( UINT8 ubProfileID, INT16 sSlotNum ) { INVENTORY_IN_SLOT *pInvSlot = GetPtrToOfferSlotWhereThisItemIs( ubProfileID, sSlotNum ); if( pInvSlot == NULL ) // not found in either offer area return( FALSE ); // found it, display it hatched out return( TRUE ); } UINT32 CalculateTotalArmsDealerCost() { UINT32 uiTotal=0; for ( int uiCnt = 0; uiCnt< gDealersOfferActiveRegions; ++uiCnt ) { if( ArmsDealerOfferArea[ uiCnt ].fActive ) { //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) uiTotal += CalculateObjectItemRepairCost( gbSelectedArmsDealerID, &( ArmsDealerOfferArea[ uiCnt ].ItemObject ) ); else uiTotal += CalcShopKeeperItemPrice( DEALER_SELLING, FALSE, ArmsDealerOfferArea[ uiCnt ].sItemIndex, armsDealerInfo[ gbSelectedArmsDealerID ].dSellModifier, &ArmsDealerOfferArea[uiCnt].ItemObject ); } } return( uiTotal ); } UINT32 CalculateTotalPlayersValue() { // we can't substitute money for intel if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) return 0; UINT32 uiTotal = 0; for ( int ubCnt = 0; ubCnt < gPlayersOfferActiveRegions; ++ubCnt ) { if( ( PlayersOfferArea[ ubCnt ].fActive ) && ( PlayersOfferArea[ ubCnt ].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE ) ) { //Calculate a price for the item if( Item[ PlayersOfferArea[ ubCnt ].sItemIndex ].usItemClass == IC_MONEY ) uiTotal += PlayersOfferArea[ ubCnt ].ItemObject[0]->data.money.uiMoneyAmount; else uiTotal += PlayersOfferArea[ ubCnt ].uiItemPrice; } } return( uiTotal ); } void PerformTransaction( UINT32 uiMoneyFromPlayersAccount ) { UINT32 uiPlayersTotalMoneyValue = CalculateTotalPlayersValue() + uiMoneyFromPlayersAccount; UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost(); UINT32 uiMoneyInPlayersOfferArea = CalculateHowMuchMoneyIsInPlayersOfferArea( ); INT32 iChangeToGiveToPlayer = 0; int iAvailablePlayerOfferSlots; //if the player has already requested to leave, get out if( gfUserHasRequestedToLeave ) return; // handle bribing... if the player is giving the dealer money, without buying anything if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 ) { // accept/refuse money (varies by dealer according to ACCEPTS_GIFTS flag) StartShopKeeperTalking( SK_QUOTES_DEALER_OFFERED_MONEY_AS_A_GIFT ); // if the arms dealer is the kind of person who accepts gifts if( armsDealerInfo[ gbSelectedArmsDealerID ].uiFlags & ARMS_DEALER_ACCEPTS_GIFTS ) { //Move all the players evaluated items to the dealer (also adds it to dealer's cash) MovePlayerOfferedItemsOfValueToArmsDealersInventory(); DealerGetsBribed( armsDealerInfo[ gbSelectedArmsDealerID ].ubShopKeeperID, uiMoneyInPlayersOfferArea ); } } else // not a bribe { //if the dealer is not a repairman if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { iAvailablePlayerOfferSlots = (gPlayersOfferActiveRegions - CountNumberOfValuelessItemsInThePlayersOfferArea( )); // always reserve an empty slot for change. We can't tell for sure whether player is getting change because that // could depend on how many items he's trying to buy won't fit - he could only be getting change because of that! if ( iAvailablePlayerOfferSlots > 0 ) { --iAvailablePlayerOfferSlots; } //if there is NOT enough room in the players offer area if( CountNumberOfItemsInTheArmsDealersOfferArea( ) > iAvailablePlayerOfferSlots ) { // tell player there's not enough room in the player's offer area DoSkiMessageBox( MSG_BOX_BASIC_STYLE, SKI_Text[ SKI_TEXT_NO_MORE_ROOM_IN_PLAYER_OFFER_AREA ], SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, NULL ); return; } } //if the player doesn't have enough money to pay for what he's buying if( uiArmsDealersItemsCost > uiPlayersTotalMoneyValue ) { INT32 balance = LaptopSaveInfo.iCurrentBalance; if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) balance = (INT32)GetIntel(); //if the player doesn't have enough money in his account to pay the rest if( uiArmsDealersItemsCost > uiPlayersTotalMoneyValue + balance ) { // tell player he can't possibly afford this SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 6,0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SKIP_A_FRAME, 0,0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 0, ( uiArmsDealersItemsCost - ( balance + uiPlayersTotalMoneyValue ) ), 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); } else { // player doesn't have enough on the table, but can pay for it from his balance /// ask player if wants to subtract the shortfall directly from his balance SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SKIP_A_FRAME, 0,0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 6,0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) { if ( uiPlayersTotalMoneyValue ) SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 8, ( uiArmsDealersItemsCost - uiPlayersTotalMoneyValue ), 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); else SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 9, ( uiArmsDealersItemsCost - uiPlayersTotalMoneyValue ), 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); } else { if ( uiPlayersTotalMoneyValue ) SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 1, ( uiArmsDealersItemsCost - uiPlayersTotalMoneyValue ), 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); else SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 2, ( uiArmsDealersItemsCost - uiPlayersTotalMoneyValue ), 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); } } SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 7,0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); gfResetShopKeepIdleQuote = TRUE; // disable further calls to PerformTransaction() until callback calls us again to free this flag gfPerformTransactionInProgress = TRUE; return; } // to get this far, player should have the money needed! Assert( uiPlayersTotalMoneyValue >= uiArmsDealersItemsCost ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { //Make sure there is enough money in the Player's offer area to cover the repair charge if( ( uiMoneyFromPlayersAccount + uiMoneyInPlayersOfferArea ) >= uiArmsDealersItemsCost ) { //Move all the players evaluated items to the dealer ( For a repairman, this means just: get rid of the money ) MovePlayerOfferedItemsOfValueToArmsDealersInventory(); //if the arms dealer is the type of person to give change if( armsDealerInfo[ gbSelectedArmsDealerID ].uiFlags & ARMS_DEALER_GIVES_CHANGE ) { //determine the amount of change to give iChangeToGiveToPlayer = ( uiMoneyFromPlayersAccount + uiMoneyInPlayersOfferArea ) - uiArmsDealersItemsCost; //if there is change if( iChangeToGiveToPlayer > 0 ) { GivePlayerSomeChange( iChangeToGiveToPlayer ); if( uiArmsDealersItemsCost > 0 ) { StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_TOO_MUCH_MONEY_FOR_TRANSACTION ); } } else StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION ); } // move the offered items to the repairman's inventory MovePlayersItemsToBeRepairedToArmsDealersInventory(); } else { //the player doesn't have enough money return; } } else // non-repairman { // DON'T include uiMoneyFromPlayersAccount in change given to player. The only way this can happen is if he agrees // to subtract from his balance, but there isn't enough room. In that case, the cost of items not transfered is // used to reduce his balance deduction instead (not here, in the calling function). iChangeToGiveToPlayer = CalculateTotalPlayersValue() - uiArmsDealersItemsCost; //if the arms dealer buys stuff if( ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_BUYS_ONLY ) || ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_BUYS_SELLS ) ) { // but the dealer can't afford this transaction if( iChangeToGiveToPlayer > ( INT32 ) gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash ) { // HACK HACK HACK: We forgot to write/record quote 27 for Jake, so he ALWAYS must have enough money! if ( gbSelectedArmsDealerID != ARMS_DEALER_JAKE ) { // no deal - dealer can't afford it StartShopKeeperTalking( SK_QUOTES_CANT_AFFORD_TO_BUY_OR_TOO_MUCH_TO_REPAIR ); gfResetShopKeepIdleQuote = TRUE; return; } } } //Move all the player's evaluated items to the dealer MovePlayerOfferedItemsOfValueToArmsDealersInventory(); //Move all dealers offered items to the player MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea( ); #ifdef JA2UB //JA25 UB //if the arms dealer is Raul if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //if this is the first time that the player purchased somehting form raul if( gArmsDealerStatus[ gbSelectedArmsDealerID ].fHasSoldSomethingToPlayer == FALSE ) { //if RAUL doesnt have the BARRETT anymore ie the player already bought it //if( gArmsDealersInventory[ ARMS_DEALER_PERKO ][ BARRETT ].ubTotalItems != 0 ) //{ if( gubQuest[ QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL ] == QUESTNOTSTARTED ) { //Raul Start the quest and have Raul say somehitng StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_46 ); StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_47 ); StartQuest( QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL, -1, -1 ); } //} } } #endif //if the arms dealer is the type of person to give change if( armsDealerInfo[ gbSelectedArmsDealerID ].uiFlags & ARMS_DEALER_GIVES_CHANGE ) { if( iChangeToGiveToPlayer > 0 ) { GivePlayerSomeChange( iChangeToGiveToPlayer ); /* //Remove the change the arms dealer is returning to the player if( ( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash - uiChangeToGiveToPlayer ) >= 0 ) gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash -= uiChangeToGiveToPlayer; else gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash = 0; */ if( uiArmsDealersItemsCost == 0 ) { StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION ); } else { StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_TOO_MUCH_MONEY_FOR_TRANSACTION ); } } else if( iChangeToGiveToPlayer == 0 ) StartShopKeeperTalking( SK_QUOTES_PLAYER_HAS_EXACTLY_ENOUGH_MONEY_FOR_TRANSACTION ); } #ifdef JA2UB //Bought from dealer specific stuff //if the arms dealer is Betty, set fact 403 if( gbSelectedArmsDealerID == ARMS_DEALER_BETTY ) { //if the player spent more the X if( uiMoneyFromPlayersAccount >= SKI_BETTY_MINIMUM_AMOUNT_TO_SET_FACT ) { //set the fact SetFactTrue( FACT_PLAYER_PAID_BETTY_MORE_THEN_X_FOR_ITEM ); } } //if the arms dealer is Raul, set fact 407 if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //if the player spent more the X if( uiMoneyFromPlayersAccount >= SKI_RAUL_MINIMUM_AMOUNT_TO_SET_FACT ) { //set the fact SetFactTrue( FACT_PLAYER_BOUGHT_SOMETHING_FROM_RAUL ); } } //Remeber the fact that the player has sold somehting to the player gArmsDealerStatus[ gbSelectedArmsDealerID ].fHasSoldSomethingToPlayer = TRUE; #endif //if the arms dealer is tony if( gbSelectedArmsDealerID == ARMS_DEALER_TONY ) gfHavePurchasedItemsFromTony = TRUE; //if the arms dealer is Howard( 125), set fact 222 if( gbSelectedArmsDealerID == ARMS_DEALER_HOWARD ) SetFactTrue( 222 ); } // add cash transfered from player's account directly to dealer's cash balance if( uiMoneyFromPlayersAccount > 0 ) { gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash += uiMoneyFromPlayersAccount; } //Redraw the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; gfDoneBusinessThisSession = TRUE; //The shopkeeper's inventory has changed, reinitialize InitializeShopKeeper( FALSE ); } gfResetShopKeepIdleQuote = TRUE; } void MoveAllArmsDealersItemsInOfferAreaToPlayersOfferArea( ) { //for all items in the dealers items offer area INT16 bSlotID=0; #ifdef JA2UB BOOLEAN fAddItemToPlayer=TRUE; #endif //loop through all the slots in the shopkeeper's offer area for ( int uiCnt = 0; uiCntItemObject.ubNumberOfObjects, &pInvSlot->ItemObject ); gfResetShopKeepIdleQuote = TRUE; return( TRUE ); } void MovePlayerOfferedItemsOfValueToArmsDealersInventory() { #ifdef JA2UB BOOLEAN fAddItemToDealer=TRUE; #endif //loop through all the slots in the players offer area for ( int uiCnt = 0; uiCnt < gPlayersOfferActiveRegions; ++uiCnt ) { //if there is an item here if( PlayersOfferArea[ uiCnt ].fActive ) { // and it has value if( PlayersOfferArea[ uiCnt ].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_VALUE ) { //Remove the item from the owner merc's inventory IfMercOwnedRemoveItemFromMercInv( &(PlayersOfferArea[ uiCnt ]) ); //if the item is money if( Item[ PlayersOfferArea[ uiCnt ].sItemIndex ].usItemClass == IC_MONEY ) { //add the money to the dealers 'cash' gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash += PlayersOfferArea[ uiCnt ].ItemObject[0]->data.money.uiMoneyAmount; } #ifdef JA2UB else { fAddItemToDealer = TRUE; //if the dealer is raul if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //if the item is the cannon balls if( PlayersOfferArea[ uiCnt ].sItemIndex == CLIP_CANNON_BALL )// 4499 ) { fAddItemToDealer = FALSE; } //if the item is the antique musket if( PlayersOfferArea[ uiCnt ].sItemIndex == HAND_CANNON )// 4498 ) { //Only do this if the player knew about the cannon offer if( gubQuest[ QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL ] == QUESTINPROGRESS ) { //Have raul say a quote StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_51 ); //Trigger Rauls action #52 TriggerNPCRecord( RAUL_UB, 52 ); //Remeber to discount the barrett SetJa25GeneralFlag( JA_GF__BARRETT_IS_HALF_PRICE ); } //Mark the quest done EndQuest( QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL, -1, -1 ); fAddItemToDealer = FALSE; } } //if we ARE to transfer the item if( fAddItemToDealer ) { //if the dealer doesn't strictly buy items from the player, give the item to the dealer if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_BUYS_ONLY ) { // item cease to be merc-owned during this operation AddObjectToArmsDealerInventory( gbSelectedArmsDealerID, &( PlayersOfferArea[ uiCnt ].ItemObject ) ); PlayersOfferArea[ uiCnt ].ItemObject.initialize(); } } } #else else { //if the dealer doesn't strictly buy items from the player, give the item to the dealer if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_BUYS_ONLY ) { // item cease to be merc-owned during this operation AddObjectToArmsDealerInventory( gbSelectedArmsDealerID, &( PlayersOfferArea[ uiCnt ].ItemObject ) ); PlayersOfferArea[ uiCnt ].ItemObject.initialize(); } } #endif //erase the item from the player's offer area ClearPlayersOfferSlot( uiCnt ); } } } gfResetShopKeepIdleQuote = TRUE; } void BeginSkiItemPointer( UINT8 ubSource, INT16 bSlotNum, BOOLEAN fOfferToDealerFirst ) { SGPRect Rect; /* // If we are already moving an item if ( gMoveingItem.sItemIndex != 0 ) { return; } */ switch( ubSource ) { case ARMS_DEALER_INVENTORY: //Should never get in here Assert( 0 ); return; break; case ARMS_DEALER_OFFER_AREA: //Get the item from the slot. gMoveingItem = ArmsDealerOfferArea[ bSlotNum ]; IfMercOwnedRemoveItemFromMercInv( &gMoveingItem ); //remove the item from the slot ClearArmsDealerOfferSlot( bSlotNum ); //Restrict the cursor to the arms dealer offer area, players offer area and the players inventory Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X; Rect.iTop = SKI_DEALER_OFFER_AREA_Y; Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH; Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT; if((UsingNewInventorySystem() == true)){ Rect.iBottom += 60; } gpItemPointer = &gMoveingItem.ItemObject; //if there is an owner of the item if( gMoveingItem.ubIdOfMercWhoOwnsTheItem != -1 ) { gpItemPointerSoldier = FindSoldierByProfileID( gMoveingItem.ubIdOfMercWhoOwnsTheItem, TRUE ); //make sure the soldier is not null if( gpItemPointerSoldier == NULL ) { gpItemPointerSoldier = gpSMCurrentMerc; } } else gpItemPointerSoldier = gpSMCurrentMerc; break; case PLAYERS_OFFER_AREA: //Get the item from the slot. gMoveingItem = PlayersOfferArea[ bSlotNum ]; // if the slot is overloaded (holds more objects than we have room for valid statuses of) if ( PlayersOfferArea[ bSlotNum ].ItemObject.ubNumberOfObjects > MAX_OBJECTS_PER_SLOT ) { // allow only MAX_OBJECTS_PER_SLOT of those objects to be picked up at a time // (sure it kind of sucks, but it's a lot easier than handling overloaded cursor objects in Interface Items! PlayersOfferArea[ bSlotNum ].ItemObject.MoveThisObjectTo(gMoveingItem.ItemObject, MAX_OBJECTS_PER_SLOT); } else // completely legal object { // NOTE: a merc-owned object can never be "overloaded", so no problem here IfMercOwnedRemoveItemFromMercInv( &gMoveingItem ); //remove the item from the slot ClearPlayersOfferSlot( bSlotNum ); } //Restrict the cursor to the players offer area and the players inventory Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X; Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y; Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH; Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT; if((UsingNewInventorySystem() == true)){ Rect.iBottom += 60; } gpItemPointer = &gMoveingItem.ItemObject; //if there is an owner of the item if( gMoveingItem.ubIdOfMercWhoOwnsTheItem != -1 ) { gpItemPointerSoldier = FindSoldierByProfileID( gMoveingItem.ubIdOfMercWhoOwnsTheItem, TRUE ); //make sure the soldier is not null if( gpItemPointerSoldier == NULL ) { gpItemPointerSoldier = gpSMCurrentMerc; } } else gpItemPointerSoldier = gpSMCurrentMerc; #ifdef JA2UB //ja25 ub //if the dealer is Raul if( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) { //if the item is the antique musket if( gMoveingItem.ItemObject.usItem == HAND_CANNON ) //4498 ) { if( gubQuest[ QUEST_FIND_ANTIQUE_MUSKET_FOR_RAUL ] == QUESTINPROGRESS ) { //if the player hasnt said the quote before if( !( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_49 ) ) { StartShopKeeperTalking( SK_QUOTES_NPC_SPECIFIC_49 ); gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__RAUL_SAID_QUOTE_49; } } } } #endif break; case PLAYERS_INVENTORY: // better be a valid merc pocket index, or -1 Assert( ( bSlotNum >= -1 ) && ( bSlotNum < (INT8)gpSMCurrentMerc->inv.size() ) ); // if we're supposed to store the original pocket #, but that pocket still holds more of these if ( ( bSlotNum != -1 ) && ( gpSMCurrentMerc->inv[ bSlotNum ].exists() == true ) ) { // then we can't store the pocket #, because our system can't return stacked objects bSlotNum = -1; } // First try to evaluate the item immediately like any other left-click on a merc's inventory slot would be. Only // if that doesn't work (because there isn't enough room in the player's offer area), the item is picked up into // the cursor, and may then get placed into the player's offer area directly, but it will NOT get evaluated that // way, and so has no possibility of entering the dealer's inventory (where complex items aren't permitted). if ( fOfferToDealerFirst && OfferObjectToDealer( gpItemPointer, gpSMCurrentMerc->ubProfile, bSlotNum ) ) { //Reset the cursor SetSkiCursor( CURSOR_NORMAL ); } else // not supposed to offer it, or dealer has no more room { // store the current contents of the cursor in a temporary object structure. // We have to do this before memsetting gMoveingItem, 'cause during swaps, gpItemPointer == &gMoveingItem.ItemObject! gTempObject = *gpItemPointer; //ARM: The memset was previously commented out, in order to preserve the owning merc's inv slot # during a swap of // items in an inventory slot. However, that leads to other bugs: if you picked the thing you're swapping in from // a restricted inv slot (headgear, vest, etc.), the item swapped out will end up belonging to an illegal slot, and // return there with a right click on it in the player's offer area. So now ALL items picked up here are unowned. gMoveingItem.initialize(); //Get the item from the pointer gMoveingItem.ItemObject = gTempObject; gMoveingItem.fActive = TRUE; gMoveingItem.sItemIndex = gTempObject.usItem; gMoveingItem.ubLocationOfObject = ubSource; // By necessity, these items don't belong to a slot (so you can't return them via a right click), // because it would be too much work to handle attachments, members of a stack, or even items swapped out of slots. gMoveingItem.ubIdOfMercWhoOwnsTheItem = gpSMCurrentMerc->ubProfile; gMoveingItem.bSlotIdInOtherLocation = bSlotNum; //Restrict the cursor to the players offer area and the players inventory Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X; Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y; Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH; Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT; if((UsingNewInventorySystem() == true)){ Rect.iBottom += 60; } gpItemPointer = &gMoveingItem.ItemObject; gpItemPointerSoldier = gpSMCurrentMerc; } break; } // if we have something in hand if ( gpItemPointer != NULL ) { //make sure the soldier is not null Assert( gpItemPointerSoldier != NULL ); // Set mouse SetSkiCursor( EXTERN_CURSOR ); //Enable the region that limits the movement of the cursor with the item MSYS_EnableRegion( &gSkiInventoryMovementAreaMouseRegions ); RestrictMouseCursor( &Rect ); DisableAllDealersInventorySlots(); if ( ubSource != ARMS_DEALER_OFFER_AREA ) { DisableAllDealersOfferSlots(); } gfResetShopKeepIdleQuote = TRUE; } gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } void RestrictSkiMouseCursor() { SGPRect Rect; Rect.iLeft = 0;//SKI_ITEM_MOVEMENT_AREA_X; Rect.iTop = SKI_ITEM_MOVEMENT_AREA_Y; Rect.iRight = SKI_ITEM_MOVEMENT_AREA_X + SKI_ITEM_MOVEMENT_AREA_WIDTH; Rect.iBottom = SKI_ITEM_MOVEMENT_AREA_Y + SKI_ITEM_MOVEMENT_AREA_HEIGHT; RestrictMouseCursor( &Rect ); } void SetSkiCursor( UINT16 usCursor ) { int ubCnt; //if we are setting up an item as a cursor if( usCursor == EXTERN_CURSOR ) { // EnableSMPanelButtons( FALSE, FALSE ); // if the current merc is in range if( !gfSMDisableForItems ) { // hatch out unavailable merc inventory slots ReevaluateItemHatches( gpSMCurrentMerc, FALSE ); } // Set mouse guiExternVo = GetInterfaceGraphicForItem( &(Item[ gMoveingItem.sItemIndex ]) ); gusExternVoSubIndex = g_bUsePngItemImages ? 0 : Item[ gMoveingItem.sItemIndex ].ubGraphicNum; SetCurrentCursorFromDatabase( EXTERN_CURSOR ); MSYS_ChangeRegionCursor( &gSMPanelRegion, usCursor ); MSYS_ChangeRegionCursor( &gSKI_EntireScreenMouseRegions, usCursor ); MSYS_ChangeRegionCursor( &gArmsDealersDropItemToGroundMouseRegions, usCursor ); MSYS_SetCurrentCursor( usCursor ); //if the item desc window is up if( gInvDesc.uiFlags & MSYS_REGION_EXISTS ) MSYS_ChangeRegionCursor( &gInvDesc, usCursor ); for( ubCnt = 0; ubCnt < gMoveingItem.ItemObject[0]->attachments.size(); ++ubCnt) { if( gItemDescAttachmentRegions[ubCnt].uiFlags & MSYS_REGION_EXISTS ) MSYS_ChangeRegionCursor( &gItemDescAttachmentRegions[ubCnt], usCursor ); } for ( ubCnt = 0; ubCntattachments.size(); ++ubCnt) { if( gItemDescAttachmentRegions[ubCnt].uiFlags & MSYS_REGION_EXISTS ) MSYS_ChangeRegionCursor( &gItemDescAttachmentRegions[ubCnt], usCursor ); } MSYS_ChangeRegionCursor( &gSkiInventoryMovementAreaMouseRegions, usCursor ); MSYS_SetCurrentCursor( usCursor ); SetCurrentCursorFromDatabase( usCursor ); FreeMouseCursor( TRUE ); EnableAllDealersInventorySlots(); EnableAllDealersOfferSlots(); //Disable the region that limits the movement of the cursor with the item MSYS_DisableRegion( &gSkiInventoryMovementAreaMouseRegions ); } SetCurrentCursorFromDatabase( usCursor ); // make sure disabled slot hatching gets updated when items picked up / dropped gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } INT16 AddInventoryToSkiLocation( INVENTORY_IN_SLOT *pInv, UINT16 ubSpotLocation, UINT8 ubWhere ) { INT16 bSlotAddedTo = -1; switch( ubWhere ) { case ARMS_DEALER_INVENTORY: case PLAYERS_INVENTORY: // not used this way Assert( 0 ); return( bSlotAddedTo ); break; case ARMS_DEALER_OFFER_AREA: //If we can add the item into the slot that was clicked on if( ArmsDealerOfferArea[ ubSpotLocation ].fActive == FALSE ) { ArmsDealerOfferArea[ ubSpotLocation ] = *pInv; IfMercOwnedCopyItemToMercInv( pInv ); SetSkiRegionHelpText( &ArmsDealerOfferArea[ ubSpotLocation ], &gDealersOfferSlotsMouseRegions[ ubSpotLocation ], ARMS_DEALER_OFFER_AREA ); SetSkiFaceRegionHelpText( &ArmsDealerOfferArea[ ubSpotLocation ], &gDealersOfferSlotsSmallFaceMouseRegions[ ubSpotLocation ], ARMS_DEALER_OFFER_AREA ); bSlotAddedTo = ubSpotLocation; } else { bSlotAddedTo = AddItemToArmsDealerOfferArea( pInv, -1 ); } break; case PLAYERS_OFFER_AREA: //If we can add the item into the slot that was clicked on if( PlayersOfferArea[ ubSpotLocation ].fActive == FALSE ) { // put it down in that player offer area slot PlayersOfferArea[ ubSpotLocation ] = *pInv; IfMercOwnedCopyItemToMercInv( pInv ); //if the item is money if( Item[ PlayersOfferArea[ ubSpotLocation ].sItemIndex ].usItemClass == IC_MONEY && !(armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL) ) { //Since money is always evaluated PlayersOfferArea[ ubSpotLocation ].uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE; PlayersOfferArea[ ubSpotLocation ].uiItemPrice = PlayersOfferArea[ ubSpotLocation ].ItemObject[0]->data.money.uiMoneyAmount; } SetSkiRegionHelpText( &PlayersOfferArea[ ubSpotLocation ], &gPlayersOfferSlotsMouseRegions[ ubSpotLocation ], PLAYERS_OFFER_AREA ); SetSkiFaceRegionHelpText( &PlayersOfferArea[ ubSpotLocation ], &gPlayersOfferSlotsSmallFaceMouseRegions[ ubSpotLocation ], PLAYERS_OFFER_AREA ); bSlotAddedTo = ubSpotLocation; } // else if( ) // { //check to see the type we are adding is the same as the type that is already there // } else // that slot is full { // add it elsewhere bSlotAddedTo = AddItemToPlayersOfferArea( pInv->ubIdOfMercWhoOwnsTheItem, pInv, pInv->bSlotIdInOtherLocation ); } break; } //Redraw the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL1; gfResetShopKeepIdleQuote = TRUE; fInterfacePanelDirty = DIRTYLEVEL2; return( bSlotAddedTo ); } void DisplayTalkingArmsDealer() { static BOOLEAN fWasTheMercTalking= FALSE; // static UINT32 uiLastTime=0; // UINT32 uiCurTime = GetJA2Clock(); // static UINT32 uiMinimumLengthForTalkingText; //Make sure the Dealers doesn't get disabled if ( giShopKeeperFaceIndex > -1 ) gFacesData[ giShopKeeperFaceIndex ].fDisabled = FALSE; HandleDialogue(); //Gets handled when we render the tactical interface HandleAutoFaces( ); HandleTalkingAutoFaces( ); HandleShopKeeperDialog( 0 ); gfIsTheShopKeeperTalking = FALSE; if ( giShopKeeperFaceIndex > -1 ) gfIsTheShopKeeperTalking = gFacesData[ giShopKeeperFaceIndex ].fTalking; //if the merc just started talking // if( gfIsTheShopKeeperTalking && !fWasTheMercTalking ) { } //if the merc is talking if( gfIsTheShopKeeperTalking ) fWasTheMercTalking = TRUE; //if the merc just finished talking if( !gfIsTheShopKeeperTalking && fWasTheMercTalking ) { RemoveShopKeeperSubTitledText(); fWasTheMercTalking = FALSE; } } void HandleShopKeeperDialog( UINT8 ubInit ) { UINT32 uiCurTime = GetJA2Clock(); static UINT32 uiLastTime = 0; static UINT32 uiLastTimeSpoke = 0; static INT8 bSpeech = -1; if( ubInit >= 1 ) { uiLastTime = GetJA2Clock(); } if( ubInit == 1 ) { // special: if it's Arnie, and we have stuff in for repairs, but it's not fixed yet, use a different opening quote! if ( ( gbSelectedArmsDealerID == ARMS_DEALER_ARNIE ) && RepairmanIsFixingItemsButNoneAreDoneYet( ARNIE ) ) { bSpeech = ARNIE_QUOTE_NOT_REPAIRED_YET; } // if repairs were delayed if ( gfStartWithRepairsDelayedQuote ) { bSpeech = FREDO_PERKO_SORRY_REPAIR_DELAYED; gfStartWithRepairsDelayedQuote = FALSE; // treat this as having done business - the player WAS here for a good reason, and it's the dealers fault... gfDoneBusinessThisSession = TRUE; } else { bSpeech = SK_QUOTES_PLAYER_FIRST_ENTERS_SKI; } return; } //if its the first time in if( bSpeech != -1 ) { if( ( uiCurTime - uiLastTime ) > 800 ) { uiLastTimeSpoke = GetJA2Clock(); StartShopKeeperTalking( bSpeech ); bSpeech = -1; uiLastTime = GetJA2Clock(); } } //Handle Idle converstions if( gfIsTheShopKeeperTalking ) { uiLastTime = uiCurTime; } //if the player has requested to leave else if( gfUserHasRequestedToLeave && ( giShopKeepDialogueEventinProgress == -1 ) ) { // to see if the player has finished talking if( !gFacesData[ giShopKeeperFaceIndex ].fTalking ) { SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 5, 0, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); } DealWithItemsStillOnTheTable(); } else { // Determine if the shopkeeper should say a quote if( ( uiCurTime - uiLastTime ) > ( guiRandomQuoteDelayTime + Random( guiRandomQuoteDelayTime ) ) ) { //Only say it occasionally if( Chance ( 35 ) ) { INT16 sRandomQuoteToUse = -1; // first check if one of the situation warrants one of the more precise quotes //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { // if there are items in the arms dealer's offer area (must be awaiting repairs) if ( CountNumberOfItemsInTheArmsDealersOfferArea() > 0) { sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_SELL_OR_REPAIR; } } else // non-repairman { // if there is any items in the player's area with value to this dealer other than just money if ( ( CalculateTotalPlayersValue() - CalculateHowMuchMoneyIsInPlayersOfferArea( ) ) > 0 ) { // if the player has also selected some items for purchase if ( CountNumberOfItemsInTheArmsDealersOfferArea() > 0 ) { sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_TRADE; } else { sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_ITEMS_CHOSEN_TO_SELL_OR_REPAIR; } } } #ifdef JA2UB //ja25 UB // if neither of the more precise quotes fit, or 33 percent of the time anyways if ( ( sRandomQuoteToUse == -1 ) || Chance( 33 ) ) { //If the dealer is Betty, if( gbSelectedArmsDealerID == ARMS_DEALER_BETTY ) { //she has 4 random quotes UINT8 ubRandom = Random( 100 ); if( ubRandom > 75 ) sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_1; else if( ubRandom > 50 ) sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_2; else if( ubRandom > 25 ) sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_3; else sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_4; } else { if( Chance ( 50 ) ) sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_1; else sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_2; } } Assert( sRandomQuoteToUse != -1 ); Assert( sRandomQuoteToUse < NUM_COMMON_SK_QUOTES ); if ( !gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] ) { StartShopKeeperTalking( (UINT16) sRandomQuoteToUse ); gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] = TRUE; //increase the random quote delay guiRandomQuoteDelayTime += SKI_DEALERS_RANDOM_QUOTE_DELAY_INCREASE_RATE; } #else // if neither of the more precise quotes fit, or 33 percent of the time anyways if ( ( sRandomQuoteToUse == -1 ) || Chance( 33 ) ) { if( Chance ( 50 ) ) sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_1; else sRandomQuoteToUse = SK_QUOTES_RANDOM_QUOTE_WHILE_PLAYER_DECIDING_2; } Assert( sRandomQuoteToUse != -1 ); Assert( sRandomQuoteToUse < NUM_COMMON_SK_QUOTES ); if ( !gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] ) { StartShopKeeperTalking( (UINT16) sRandomQuoteToUse ); gfCommonQuoteUsedThisSession[ sRandomQuoteToUse ] = TRUE; //increase the random quote delay guiRandomQuoteDelayTime += SKI_DEALERS_RANDOM_QUOTE_DELAY_INCREASE_RATE; } #endif } uiLastTime = GetJA2Clock(); } } } BOOLEAN StartShopKeeperTalking( UINT16 usQuoteNum ) { BOOLEAN fSuccess; // if already in the process of leaving, don't start any additional quotes if ( gfSKIScreenExit || gfRemindedPlayerToPickUpHisStuff || gfUserHasRequestedToLeave ) { return( FALSE ); } // post event to mark shopkeeper dialogue in progress SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 3, usQuoteNum, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); // post quote dialogue fSuccess = CharacterDialogue( armsDealerInfo[ gbSelectedArmsDealerID ].ubShopKeeperID, usQuoteNum, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI, FALSE, FALSE ); // post event to mark shopkeeper dialogue as ended SpecialCharacterDialogueEvent( DIALOGUE_SPECIAL_EVENT_SHOPKEEPER, 4, usQuoteNum, 0, giShopKeeperFaceIndex, DIALOGUE_SHOPKEEPER_UI ); if ( !fSuccess ) { return( FALSE ); } gfResetShopKeepIdleQuote = TRUE; return( TRUE ); } BOOLEAN IsGunOrAmmoOfSameTypeSelected( OBJECTTYPE *pItemObject ) { //if there is no item selected, return if( gpHighLightedItemObject == NULL ) return( FALSE ); //if the item is ammo if( Item[ gpHighLightedItemObject->usItem ].usItemClass == IC_AMMO ) { //if there is a gun if( Item[ pItemObject->usItem ].usItemClass == IC_GUN ) { //of the same caliber if( Weapon[ pItemObject->usItem ].ubCalibre == Magazine[ Item[ gpHighLightedItemObject->usItem ].ubClassIndex ].ubCalibre ) { return( TRUE ); } } } //else if the item is a gun else if( Item[ gpHighLightedItemObject->usItem ].usItemClass == IC_GUN ) { //if there is a gun if( Item[ pItemObject->usItem ].usItemClass == IC_AMMO ) { //of the same caliber if( Weapon[ gpHighLightedItemObject->usItem ].ubCalibre == Magazine[ Item[ pItemObject->usItem ].ubClassIndex ].ubCalibre ) { return( TRUE ); } } } //if the highlighted object is an attachment if(ItemIsAttachment(pItemObject->usItem)) { if( ValidAttachment( pItemObject->usItem, gpHighLightedItemObject ) ) return( TRUE ); } else { if( ValidAttachment( gpHighLightedItemObject->usItem, pItemObject ) ) return( TRUE ); } return( FALSE ); } void InitShopKeeperSubTitledText( STR16 pString ) { //Clear the contents of the subtitle text memset(gsShopKeeperTalkingText, 0, sizeof(CHAR16)* SKI_SUBTITLE_TEXT_SIZE); swprintf( gsShopKeeperTalkingText, L"\"%s\"", pString ); //Now setup the popup box if( gGameSettings.fOptions[ TOPTION_SUBTITLES ] ) { UINT16 usActualWidth=0; UINT16 usActualHeight=0; giPopUpBoxId = PrepareMercPopupBox( giPopUpBoxId, BASIC_MERC_POPUP_BACKGROUND, BASIC_MERC_POPUP_BORDER, gsShopKeeperTalkingText, 300, 0, 0, 0, &usActualWidth, &usActualHeight); // gusPositionOfSubTitlesX = ( 640 - usActualWidth ) / 2 ; //position it to start under the guys face gusPositionOfSubTitlesX = 13 + SCREEN_X_OFFSET; RenderMercPopUpBoxFromIndex( giPopUpBoxId, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, FRAME_BUFFER); //check to make sure the region is not already initialized if( !( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS ) ) { MSYS_DefineRegion( &gShopKeeperSubTitleMouseRegion, gusPositionOfSubTitlesX, SKI_POSITION_SUBTITLES_Y, (INT16)(gusPositionOfSubTitlesX + usActualWidth), (INT16)(SKI_POSITION_SUBTITLES_Y + usActualHeight), MSYS_PRIORITY_HIGH, CURSOR_NORMAL, MSYS_NO_CALLBACK, ShopKeeperSubTitleRegionCallBack ); MSYS_AddRegion( &gShopKeeperSubTitleMouseRegion ); } //redraw the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } void RemoveShopKeeperSubTitledText() { if( giPopUpBoxId == -1 ) return; if( RemoveMercPopupBoxFromIndex( giPopUpBoxId ) ) { giPopUpBoxId = -1; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; //Get rid of the subtitles region if( gGameSettings.fOptions[ TOPTION_SUBTITLES ] ) MSYS_RemoveRegion( &gShopKeeperSubTitleMouseRegion ); } } BOOLEAN AreThereItemsInTheArmsDealersOfferArea( ) { //loop through the players offer area and see if there are any items there for ( int ubCnt = 0; ubCnt < gDealersOfferActiveRegions; ++ubCnt ) { //if is an item here if( ArmsDealerOfferArea[ ubCnt ].fActive ) { return( TRUE ); } } return( FALSE ); } BOOLEAN AreThereItemsInThePlayersOfferArea( ) { //loop through the players offer area and see if there are any items there for ( int ubCnt = 0; ubCnt < gPlayersOfferActiveRegions; ++ubCnt ) { //if is an item here if( PlayersOfferArea[ ubCnt ].fActive ) { return( TRUE ); } } return( FALSE ); } //Mouse Call back for the Arms traders inventory slot void ShopKeeperSubTitleRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_LBUTTON_UP || iReason & MSYS_CALLBACK_REASON_RBUTTON_UP) { ShutUpShopKeeper(); } } //Mouse Call back for the Arms delaers face void SelectArmsDealersFaceRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_LBUTTON_UP || iReason & MSYS_CALLBACK_REASON_RBUTTON_UP) { ShutUpShopKeeper(); } } void ShutUpShopKeeper() { // RemoveShopKeeperSubTitledText(); ShutupaYoFace( giShopKeeperFaceIndex ); gfIsTheShopKeeperTalking = FALSE; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } int CountNumberOfValuelessItemsInThePlayersOfferArea( ) { int count = 0; //loop through the players offer area and see if there are any items there for ( int ubCnt = 0; ubCnt= uiArmsDealerTotalCost ) EnableButton( guiSKI_TransactionButton ); // else // DisableButton( guiSKI_TransactionButton ); /* //if the only item in the players offer area is money, and there is nothing in the dealers offer area if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && uiArmsDealerTotalCost == 0 ) DisableButton( guiSKI_TransactionButton ); */ //If its a repair dealer, and there is no items in the Dealer Offer area, if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS && CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 && uiPlayersOfferAreaTotalMoney < uiArmsDealerTotalCost ) DisableButton( guiSKI_TransactionButton ); } //else if there is else if( uiArmsDealerTotalCost != 0 ) { EnableButton( guiSKI_TransactionButton ); } else { DisableButton( guiSKI_TransactionButton ); } INT32 balance = LaptopSaveInfo.iCurrentBalance; if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) balance = (INT32)GetIntel(); if( uiArmsDealerTotalCost > uiPlayersOfferAreaTotalCost + balance ) { DisableButton( guiSKI_TransactionButton ); } /* Allow transaction attempt when dealer can't afford to buy that much - he'll reject it with a special quote! else if( ( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash + uiArmsDealerTotalCost ) < ( uiPlayersOfferAreaTotalCost - uiPlayersOfferAreaTotalMoney ) ) { DisableButton( guiSKI_TransactionButton ); } */ else if( ( uiPlayersOfferAreaTotalCost == uiPlayersOfferAreaTotalMoney ) && ( uiPlayersOfferAreaTotalMoney > 0 ) && ( uiArmsDealerTotalCost == 0 ) ) { DisableButton( guiSKI_TransactionButton ); } //if the player is currently moving an item, disable the transaction button if( gMoveingItem.sItemIndex != 0 ) { EnableDisableShopkeeperButtons(SHOPKEEPER_SCREEN, DISABLE_BUTTON); } else { EnableButton( guiSKI_DoneButton ); } //ARM: Always permit trying bribes, even if they don't work on a given dealer! // if the arms dealer is the kind of person who accepts gifts, and there is stuff to take // if( armsDealerInfo[ gbSelectedArmsDealerID ].uiFlags & ARMS_DEALER_ACCEPTS_GIFTS ) { //if the player is giving the dealer money, without buying anything if( IsMoneyTheOnlyItemInThePlayersOfferArea( ) && CountNumberOfItemsInTheArmsDealersOfferArea( ) == 0 ) { EnableButton( guiSKI_TransactionButton ); } } if( gfUserHasRequestedToLeave ) DisableButton( guiSKI_TransactionButton ); //if the player is in the item desc for the arms dealer items if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL ) { ButtonList[ guiSKI_TransactionButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; ButtonList[ guiSKI_DoneButton ]->uiFlags |= BUTTON_FORCE_UNDIRTY; } else { ButtonList[ guiSKI_TransactionButton ]->uiFlags &= ~BUTTON_FORCE_UNDIRTY; ButtonList[ guiSKI_DoneButton ]->uiFlags &= ~BUTTON_FORCE_UNDIRTY; } } void AddItemToPlayersOfferAreaAfterShopKeeperOpen( OBJECTTYPE *pItemObject) { gItemToAdd.fActive = TRUE; gItemToAdd.ItemObject = *pItemObject; } BOOLEAN IsMoneyTheOnlyItemInThePlayersOfferArea( ) { // we can't substitute money for intel if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) return FALSE; BOOLEAN fFoundMoney = FALSE; for ( int ubCnt = 0; ubCnt < gPlayersOfferActiveRegions; ++ubCnt ) { //if there is an item here if( PlayersOfferArea[ ubCnt ].fActive ) { if( Item[ PlayersOfferArea[ ubCnt ].sItemIndex ].usItemClass != IC_MONEY ) return( FALSE ); else fFoundMoney = TRUE; } } if ( fFoundMoney ) Assert( CalculateHowMuchMoneyIsInPlayersOfferArea() > 0 ); // only return TRUE if there IS money in the POA return( fFoundMoney ); } UINT32 CalculateHowMuchMoneyIsInPlayersOfferArea( ) { // we can't substitute money for intel if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) return 0; UINT32 uiTotalMoneyValue=0; for ( int ubCnt = 0; ubCnt < gPlayersOfferActiveRegions; ++ubCnt ) { //if there is an item here if( PlayersOfferArea[ ubCnt ].fActive ) { if( Item[ PlayersOfferArea[ ubCnt ].sItemIndex ].usItemClass == IC_MONEY ) { uiTotalMoneyValue += PlayersOfferArea[ ubCnt ].ItemObject[0]->data.money.uiMoneyAmount; } } } return( uiTotalMoneyValue ); } void MovePlayersItemsToBeRepairedToArmsDealersInventory() { //loop through all the slots in the shopkeeper's offer area for ( int uiCnt = 0; uiCntinv.size(); for( INT8 bCnt=0; bCnt < invsize; ++bCnt) { // Look for MONEY only, not Gold or Silver!!! And look for a slot not already full if( ( pSoldier->inv[ bCnt ].usItem == MONEY ) && ( pSoldier->inv[ bCnt ][0]->data.money.uiMoneyAmount < MoneySlotLimit( bCnt ) ) ) { return( bCnt ); } } return( NO_SLOT ); } void ClearArmsDealerOfferSlot( INT32 ubSlotToClear ) { // Clear the contents ArmsDealerOfferArea[ ubSlotToClear ].initialize(); //Remove the mouse help text from the region SetRegionFastHelpText( &gDealersOfferSlotsMouseRegions[ ubSlotToClear ], L"" ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { SetRegionFastHelpText( &gDealersOfferSlotsSmallFaceMouseRegions[ ubSlotToClear ], L"" ); } } void ClearPlayersOfferSlot( INT32 ubSlotToClear ) { // Clear the contents PlayersOfferArea[ ubSlotToClear ].initialize(); //Clear the text for the item SetRegionFastHelpText( &gPlayersOfferSlotsMouseRegions[ ubSlotToClear ], L"" ); SetRegionFastHelpText( &gPlayersOfferSlotsSmallFaceMouseRegions[ ubSlotToClear ], L"" ); // if the player offer area is clear, reset flags for transaction CheckAndHandleClearingOfPlayerOfferArea( ); } void EvaluateItemAddedToPlayersOfferArea( INT8 bSlotID, BOOLEAN fFirstOne ) { UINT32 uiEvalResult = EVAL_RESULT_NORMAL; BOOLEAN fRocketRifleWasEvaluated = FALSE; UINT8 ubNumberOfItemsAddedToRepairDuringThisEvaluation=0; // there better be an item there Assert ( PlayersOfferArea[ bSlotID ].fActive ); //if money is the item being evaluated, leave if( Item[ PlayersOfferArea[ bSlotID ].sItemIndex ].usItemClass == IC_MONEY ) return; // if already evaluated, don't do it again if( PlayersOfferArea[ bSlotID ].uiFlags & ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED ) { return; } // say "Hmm... Let's see" once per trading session to start evaluation // SPECIAL: Devin doesn't have this quote (he's the only one) #ifdef JA2UB //no UB #else if( !gfDealerHasSaidTheEvaluateQuoteOnceThisSession && ( gbSelectedArmsDealerID != ARMS_DEALER_DEVIN ) ) { gfDealerHasSaidTheEvaluateQuoteOnceThisSession = TRUE; StartShopKeeperTalking( SK_QUOTES_PLAYER_REQUESTED_EVALUATION ); } #endif //Can this particular kind of item be sold/repaired here if( WillShopKeeperRejectObjectsFromPlayer( gbSelectedArmsDealerID, bSlotID ) == FALSE ) { //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { int uiNumberOfItemsInForRepairs = CountTotalItemsRepairDealerHasInForRepairs( gbSelectedArmsDealerID ); int uiNumberOfItemsAlreadyEvaluated = CountNumberOfItemsInTheArmsDealersOfferArea(); //Get the number of items being evaluated ubNumberOfItemsAddedToRepairDuringThisEvaluation = PlayersOfferArea[ bSlotID ].ItemObject.ubNumberOfObjects; //if there is already enough items in for repairs, complain about it and DON'T accept the item for repairs if( ( uiNumberOfItemsAlreadyEvaluated + ubNumberOfItemsAddedToRepairDuringThisEvaluation + uiNumberOfItemsInForRepairs ) > SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME ) { if ( !gfAlreadySaidTooMuchToRepair ) { //Start the dealer talking StartShopKeeperTalking( SK_QUOTES_CANT_AFFORD_TO_BUY_OR_TOO_MUCH_TO_REPAIR ); gfAlreadySaidTooMuchToRepair = TRUE; } return; } //if the item is a rocket rifle if(ItemHasFingerPrintID( PlayersOfferArea[ bSlotID ].sItemIndex ) ) { fRocketRifleWasEvaluated = TRUE; } //if the item is damaged, or is a rocket rifle (which always "need repairing" even at 100%, to reset imprinting) if( ( PlayersOfferArea[ bSlotID ].ItemObject[0]->data.objectStatus < 100 ) || fRocketRifleWasEvaluated ) { // Move the item to the Dealer's Offer Area INT16 bSlotAddedTo = AddItemToArmsDealerOfferArea( &PlayersOfferArea[ bSlotID ], PlayersOfferArea[ bSlotID ].bSlotIdInOtherLocation ); if( bSlotAddedTo != -1 ) { // Clear the contents ClearPlayersOfferSlot( bSlotID ); /* ARM: Leave it there, until transaction occurs it should be recallable //Remove the item from the owner merc's inventory IfMercOwnedRemoveItemFromMercInv( &(ArmsDealerOfferArea[ bSlotAddedTo ]) ); */ //Mark the item as unselected, signifying that it can be moved ArmsDealerOfferArea[ bSlotAddedTo ].uiFlags &= ~ARMS_INV_ITEM_SELECTED; //increment the number of items being added ++ubNumberOfItemsAddedToRepairDuringThisEvaluation; // check if the item is really badly damaged if( Item[ ArmsDealerOfferArea[ bSlotAddedTo ].sItemIndex ].usItemClass != IC_AMMO ) { if( ArmsDealerOfferArea[ bSlotAddedTo ].ItemObject[0]->data.objectStatus < REALLY_BADLY_DAMAGED_THRESHOLD ) { uiEvalResult = EVAL_RESULT_OK_BUT_REALLY_DAMAGED; } } // check if it's the first time a rocket rifle is being submitted to Fredo if( fRocketRifleWasEvaluated && ( gbSelectedArmsDealerID == ARMS_DEALER_FREDO ) ) { //if he hasn't yet said his quote if( !( gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__FREDO_HAS_SAID_ROCKET_RIFLE_QUOTE ) ) { // use this special evaluation result instead (has a unique quote) uiEvalResult = EVAL_RESULT_ROCKET_RIFLE; } } } else { #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Failed to add repair item to ArmsDealerOfferArea. AM-0"); #endif return; } } else { uiEvalResult = EVAL_RESULT_NOT_DAMAGED; } } else // not a repairman { uiEvalResult = EvaluateInvSlot( & ( PlayersOfferArea[ bSlotID ] ) ); } } else // dealer doesn't handle this type of object { if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { // only otherwise repairable items count as actual rejections if (ItemIsRepairable(PlayersOfferArea[bSlotID].sItemIndex)) { uiEvalResult = EVAL_RESULT_DONT_HANDLE; } else { uiEvalResult = EVAL_RESULT_NON_REPAIRABLE; } } else { uiEvalResult = EVAL_RESULT_DONT_HANDLE; } } // mark this item as having been evaluated PlayersOfferArea[ bSlotID ].uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_BEEN_EVALUATED; // when evaluating complex items that get split into multiple subobjects, dealer will talk only about the first one! // don't bother with any of this if shopkeeper can't talk right now if ( fFirstOne && CanShopkeeperOverrideDialogue( ) ) { INT16 sQuoteNum = -1; UINT8 ubChanceToSayNormalQuote; switch ( uiEvalResult ) { case EVAL_RESULT_DONT_HANDLE: #ifdef JA2UB //JA25 UB if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_SELLS_ONLY ) { // then he doesn't have quotes 17, 19, or 20, always use 4. Devin doesn't have 18 either, // while the text of 18 seems wrong for Sam & Howard if offered something they should consider valuable. sQuoteNum = SK_QUOTES_NOT_INTERESTED_IN_THIS_ITEM; } else { //if the dealer is BETTY if( gbSelectedArmsDealerID == ARMS_DEALER_BETTY && ( Item[ PlayersOfferArea[ bSlotID ].sItemIndex ].usItemClass == IC_AMMO || Item[ PlayersOfferArea[ bSlotID ].sItemIndex ].usItemClass == IC_GUN ) ) { // she accepts items, but not this one sQuoteNum = SK_QUOTES_NPC_SPECIFIC_40; } else { // he accepts items, but not this one sQuoteNum = SK_QUOTES_DURING_EVALUATION_STUFF_REJECTED; } } #endif if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_SELLS_ONLY ) { // then he doesn't have quotes 17, 19, or 20, always use 4. Devin doesn't have 18 either, // while the text of 18 seems wrong for Sam & Howard if offered something they should consider valuable. sQuoteNum = SK_QUOTES_NOT_INTERESTED_IN_THIS_ITEM; } else { // he accepts items, but not this one sQuoteNum = SK_QUOTES_DURING_EVALUATION_STUFF_REJECTED; } break; case EVAL_RESULT_NON_REPAIRABLE: // use quote 4 for this situation... It's a bit weird but better than "that looks fine / ain't broke" sQuoteNum = SK_QUOTES_NOT_INTERESTED_IN_THIS_ITEM; break; case EVAL_RESULT_NOT_DAMAGED: case EVAL_RESULT_WORTHLESS: sQuoteNum = SK_QUOTES_EVALUATION_RESULT_VALUE_OF_ZERO; break; case EVAL_RESULT_OK_BUT_REALLY_DAMAGED: sQuoteNum = SK_QUOTES_EVALUATION_RESULT_SOME_REALLY_DAMAGED_ITEMS; break; case EVAL_RESULT_ROCKET_RIFLE: sQuoteNum = FREDO_QUOTE_CAN_RESET_IMPRINTING; break; case EVAL_RESULT_NORMAL: //if it has been a long time since saying the last quote if( guiLastTimeDealerSaidNormalEvaluationQuote == 0 ) ubChanceToSayNormalQuote = 100; else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 25000 ) ubChanceToSayNormalQuote = 60; else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 12000 ) ubChanceToSayNormalQuote = 25; else if( ( GetJA2Clock() - guiLastTimeDealerSaidNormalEvaluationQuote ) > 7000 ) ubChanceToSayNormalQuote = 10; else ubChanceToSayNormalQuote = 0; if( Chance( ubChanceToSayNormalQuote ) ) { sQuoteNum = SK_QUOTES_EVALUATION_RESULT_NORMAL; } break; default: #ifdef JA2BETAVERSION ScreenMsg( FONT_MCOLOR_WHITE, MSG_BETAVERSION, L"Invalid evaluation result of %d. AM-0", uiEvalResult ); #endif break; } // if a valid quote was selected if ( sQuoteNum != -1 ) { // if the appropriate quote hasn't already been used during this evaluation cycle if ( !gfEvalResultQuoteSaid[ uiEvalResult ] ) { // try to say it if ( StartShopKeeperTalking( ( UINT16 ) sQuoteNum ) ) { // set flag to keep from repeating it gfEvalResultQuoteSaid[ uiEvalResult ] = TRUE; // if it's the normal result if ( uiEvalResult == EVAL_RESULT_NORMAL ) { // set new time of last usage guiLastTimeDealerSaidNormalEvaluationQuote = GetJA2Clock(); } if ( sQuoteNum == FREDO_QUOTE_CAN_RESET_IMPRINTING ) { //Set the fact that we have said this (to prevent that quote from coming up again) gArmsDealerStatus[ gbSelectedArmsDealerID ].ubSpecificDealerFlags |= ARMS_DEALER_FLAG__FREDO_HAS_SAID_ROCKET_RIFLE_QUOTE; } } } } } } BOOLEAN DoSkiMessageBox( UINT8 ubStyle, STR16 zString, UINT32 uiExitScreen, UINT8 ubFlags, MSGBOX_CALLBACK ReturnCallback ) { SGPRect pCenteringRect= {0, 0, SCREEN_WIDTH - 1, SCREEN_HEIGHT - INV_INTERFACE_HEIGHT }; // reset exit mode gfExitSKIDueToMessageBox = TRUE; // do message box and return giSKIMessageBox = DoMessageBox( ubStyle, zString, uiExitScreen, ( UINT8 ) ( ubFlags| MSG_BOX_FLAG_USE_CENTERING_RECT ), ReturnCallback, &pCenteringRect ); // send back return state return( ( giSKIMessageBox != -1 ) ); } void ConfirmDontHaveEnoughForTheDealerMessageBoxCallBack( UINT8 bExitValue ) { // simply redraw the panel if( bExitValue == MSG_BOX_RETURN_OK ) { gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } // done, re-enable calls to PerformTransaction() gfPerformTransactionInProgress = FALSE; } void ConfirmToDeductMoneyFromPlayersAccountMessageBoxCallBack( UINT8 bExitValue ) { // yes, deduct the money if( bExitValue == MSG_BOX_RETURN_YES ) { UINT32 uiPlayersOfferAreaValue = CalculateTotalPlayersValue(); UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost(); INT32 iMoneyToDeduct = (INT32)( uiArmsDealersItemsCost - uiPlayersOfferAreaValue ); //Perform the transaction with the extra money from the players account PerformTransaction( iMoneyToDeduct ); AddTransactionToPlayersBook( PURCHASED_ITEM_FROM_DEALER, armsDealerInfo[ gbSelectedArmsDealerID ].ubShopKeeperID, GetWorldTotalMin(), -iMoneyToDeduct ); } // done, re-enable calls to PerformTransaction() gfPerformTransactionInProgress = FALSE; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } void ConfirmToDeductIntelFromPlayersAccountMessageBoxCallBack( UINT8 bExitValue ) { // yes, deduct the money if ( bExitValue == MSG_BOX_RETURN_YES ) { UINT32 uiPlayersOfferAreaValue = CalculateTotalPlayersValue(); UINT32 uiArmsDealersItemsCost = CalculateTotalArmsDealerCost(); INT32 iMoneyToDeduct = (INT32)( uiArmsDealersItemsCost - uiPlayersOfferAreaValue ); //Perform the transaction with the extra money from the players account PerformTransaction( iMoneyToDeduct ); AddIntel( (FLOAT)-iMoneyToDeduct, TRUE ); } // done, re-enable calls to PerformTransaction() gfPerformTransactionInProgress = FALSE; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } // run through what the player has on the table and see if the shop keep will aceept it or not BOOLEAN WillShopKeeperRejectItemFromPlayer( INT8 bDealerId, UINT16 usItem ) { BOOLEAN fRejected = TRUE; if ( Item[usItem].usItemClass == IC_MONEY ) { // we can't substitute money for intel if ( armsDealerInfo[gbSelectedArmsDealerID].uiFlags & ARMS_DEALER_DEALWITHINTEL ) fRejected = TRUE; else fRejected = FALSE; } else if ( CanDealerTransactItem( gbSelectedArmsDealerID, usItem, TRUE ) ) { fRejected = FALSE; } else { fRejected = TRUE; } return fRejected; } BOOLEAN DoesCurrentDealerRefuseToTradeItem( UINT16 usItem ) { //if non-repairman if ( armsDealerInfo[gbSelectedArmsDealerID].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { // don't evaluate anything he wouldn't buy! if ( WillShopKeeperRejectItemFromPlayer( gbSelectedArmsDealerID, usItem ) ) { return TRUE; } } return FALSE; } // run through what the player has on the table and see if the shop keep will aceept it or not BOOLEAN WillShopKeeperRejectObjectsFromPlayer( INT8 bDealerId, INT8 bSlotId ) { return WillShopKeeperRejectItemFromPlayer( bDealerId, PlayersOfferArea[bSlotId].sItemIndex ); } void CheckAndHandleClearingOfPlayerOfferArea( void ) { BOOLEAN fActiveSlot = FALSE; // find out if all the player trade slots/offer area is empty for ( int iCounter = 0; iCounter DELAY_FOR_SHOPKEEPER_IDLE_QUOTE ) { // random chance enough? if( iRand > CHANCE_FOR_SHOPKEEPER_IDLE_QUOTE ) { // is there enough on the table if( ( uiArmsDealersItemsCost > uiPlayersOfferAreaValue ) && ( uiPlayersOfferAreaValue ) ) { StartShopKeeperTalking( SK_QUOTES_EVAULATION_PLAYER_DOESNT_HAVE_ENOUGH_VALUE ); } } gfResetShopKeepIdleQuote = TRUE; } } void InitShopKeeperItemDescBox( OBJECTTYPE *pObject, UINT16 ubPocket, UINT8 ubFromLocation ) { INT16 sPosX, sPosY; bool showItemDescriptionBox = false; switch( ubFromLocation ) { case ARMS_DEALER_INVENTORY: { showItemDescriptionBox = true; UINT16 ubSelectedInvSlot = ubPocket - gSelectArmsDealerInfo.ubFirstItemIndexOnPage; if( gGameSettings.fOptions[ TOPTION_ENHANCED_DESC_BOX ] == FALSE) { sPosX = SKI_TRADER_INVENTORY_BOXES_BEGIN_X; sPosY = SKI_TRADER_INVENTORY_BOXES_BEGIN_Y; } else { sPosX = SCREEN_X_OFFSET + 2; sPosY = SCREEN_Y_OFFSET + 146; } //if the start position + the height of the box is off the screen, reposition if( sPosY < (0 + SCREEN_Y_OFFSET) ) sPosY = 0 + SCREEN_Y_OFFSET; //if the start position + the width of the box is off the screen, reposition if( ( sPosX + 358 ) > SCREEN_WIDTH ) sPosX = sPosX - ( ( sPosX + 358 ) - SCREEN_WIDTH ) - 5; //if it is starting to far to the left else if( sPosX < (0 + SCREEN_X_OFFSET) ) sPosX = 0 + SCREEN_X_OFFSET; //if the box will appear over the mercs face, move the box over so it doesn't obstruct the face if( sPosY < SKI_FACE_Y + SKI_FACE_HEIGHT + 20 ) if( sPosX < (SCREEN_X_OFFSET + 160) ) sPosX = SCREEN_X_OFFSET + 160; } break; case ARMS_DEALER_OFFER_AREA: { showItemDescriptionBox = false; sPosX = SKI_TRADER_OFFER_BOXES_BEGIN_X; sPosY = SKI_TRADER_OFFER_BOXES_BEGIN_Y; //if the start position + the height of the box is off the screen, reposition if( sPosY < (SCREEN_Y_OFFSET + 0) ) sPosY = 0 + SCREEN_Y_OFFSET; //if the start position + the width of the box is off the screen, reposition if( ( sPosX + 358 ) > SCREEN_WIDTH ) sPosX = sPosX - ( ( sPosX + 358 ) - SCREEN_WIDTH ) - 5; //if it is starting to far to the left else if( sPosX < (0 + SCREEN_X_OFFSET) ) sPosX = 10 + SCREEN_X_OFFSET; //if the box will appear over the mercs face, move the box over so it doesn't obstruct the face if( sPosY < SKI_FACE_Y + SKI_FACE_HEIGHT + 20 ) if( sPosX < (160 + SCREEN_X_OFFSET) ) sPosY = SCREEN_Y_OFFSET + INV_INTERFACE_HEIGHT; } break; default: Assert( 0 ); return; } // WANNE: Only allow right click (item desription box) in the arms dealer inventory if (showItemDescriptionBox) { pShopKeeperItemDescObject = pObject; InitItemDescriptionBox( gpSMCurrentMerc, 255, sPosX, sPosY, 0 ); StartSKIDescriptionBox( ); } } void StartSKIDescriptionBox( void ) { //shadow the entire screen DrawHatchOnInventory( FRAME_BUFFER, SKI_TOTAL_BEGIN_X, SKI_TOTAL_BEGIN_Y, SKI_TOTAL_END_X - SKI_TOTAL_BEGIN_X, SKI_TOTAL_END_Y - SKI_TOTAL_BEGIN_Y ); InvalidateRegion( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); // disable almost everything! gfSMDisableForItems = TRUE; DisableInvRegions( gfSMDisableForItems ); DisableButton( guiSKI_InvPageUpButton ); DisableButton( guiSKI_InvPageDownButton ); EnableDisableShopkeeperButtons(SHOPKEEPER_SCREEN, DISABLE_BUTTON); DisableAllDealersInventorySlots(); DisableAllDealersOfferSlots(); for ( int iCnt = 0; iCnt < gPlayersOfferActiveRegions; ++iCnt ) { MSYS_DisableRegion( &gPlayersOfferSlotsMouseRegions[ iCnt ] ); MSYS_DisableRegion( &gPlayersOfferSlotsSmallFaceMouseRegions[ iCnt ] ); } if( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS ) { MSYS_DisableRegion( &gShopKeeperSubTitleMouseRegion ); } RenderItemDescriptionBox( ); } void DeleteShopKeeperItemDescBox() { pShopKeeperItemDescObject = NULL; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; //Redraw the face if ( giShopKeeperFaceIndex > -1 ) gFacesData[ giShopKeeperFaceIndex ].uiFlags |= FACE_REDRAW_WHOLE_FACE_NEXT_FRAME; // enable almost everything! CheckForDisabledForGiveItem( ); DisableInvRegions( gfSMDisableForItems ); //actually does an enable if current merc is in range EnableButton( guiSKI_InvPageUpButton ); EnableButton( guiSKI_InvPageDownButton ); EnableDisableShopkeeperButtons(SHOPKEEPER_SCREEN, ENABLE_BUTTON); EnableDisableDealersInventoryPageButtons(); EnableDisableEvaluateAndTransactionButtons(); EnableAllDealersInventorySlots(); EnableAllDealersOfferSlots(); for ( int iCnt = 0; iCnt < gPlayersOfferActiveRegions; ++iCnt ) { MSYS_EnableRegion( &gPlayersOfferSlotsMouseRegions[ iCnt ] ); MSYS_EnableRegion( &gPlayersOfferSlotsSmallFaceMouseRegions[ iCnt ] ); } if( gShopKeeperSubTitleMouseRegion.uiFlags & MSYS_REGION_EXISTS ) { MSYS_EnableRegion( &gShopKeeperSubTitleMouseRegion ); } } BOOLEAN OfferObjectToDealer( OBJECTTYPE *pComplexObject, UINT8 ubOwnerProfileId, INT16 bOwnerSlotId ) { UINT8 ubRepairableSubObjects; UINT8 ubNonRepairableSubObjects; int ubDealerOfferAreaSlotsNeeded; int ubPlayerOfferAreaSlotsNeeded; int ubDiff; int ubHowManyMoreItemsCanDealerTake; BOOLEAN fFirstOne = TRUE; BOOLEAN fSuccess = FALSE; if ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer != ARMS_DEALER_REPAIRS ) { // if not actually doing repairs, there's no need to split objects up at all if ( !AddObjectForEvaluation( pComplexObject, ubOwnerProfileId, bOwnerSlotId, TRUE ) ) { gfDisplayNoRoomMsg = TRUE; return(FALSE); // no room } } else // dealing with a repairman { // split the object into its components SplitComplexObjectIntoSubObjects( pComplexObject ); // determine how many subobjects of each category this complex object will have to be broken up into CountSubObjectsInObject( pComplexObject, &ubRepairableSubObjects, &ubNonRepairableSubObjects ); // in the simplest situation, the # subobjects of each type gives us how many slots of each type are needed ubDealerOfferAreaSlotsNeeded = ubRepairableSubObjects; ubPlayerOfferAreaSlotsNeeded = ubNonRepairableSubObjects; // consider that if dealer is at or will reach his max # of items repaired limit, not everything repairable will move ubHowManyMoreItemsCanDealerTake = SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME - CountTotalItemsRepairDealerHasInForRepairs( gbSelectedArmsDealerID ) - CountNumberOfItemsInTheArmsDealersOfferArea(); // if he can't repair everything repairable that we're about to submit, the space we'll need changes if ( ubDealerOfferAreaSlotsNeeded > ubHowManyMoreItemsCanDealerTake ) { ubDiff = ubDealerOfferAreaSlotsNeeded - ubHowManyMoreItemsCanDealerTake; ubDealerOfferAreaSlotsNeeded -= ubDiff; ubPlayerOfferAreaSlotsNeeded += ubDiff; } // if there is anything repairable that dealer will accept if ( ubDealerOfferAreaSlotsNeeded > 0 ) { // we need at least one EXTRA empty slot in player's area to pass repairable objects through for their evaluation ++ubPlayerOfferAreaSlotsNeeded; } //this is a silly assert, was it meant to be something else? Assert( SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME < (SKI_PLAYER_OFFER_BOXES_PER_ROW * SKI_PLAYER_OFFER_BOXES_PER_COL) ); /* This code is commented out because a repair dealer will never be allowed to repair over more then SKI_MAX_AMOUNT_OF_ITEMS_DEALER_CAN_REPAIR_AT_A_TIME ( 4 ) items at a time, therefore, cant fill up the dealer offer area // check if we have room for all of each type if ( ( ubDealerOfferAreaSlotsNeeded + CountNumberOfItemsInTheArmsDealersOfferArea() ) > SKI_NUM_TRADING_INV_SLOTS ) { // tell player there's not enough room in the dealer's offer area DoSkiMessageBox( MSG_BOX_BASIC_STYLE, L"There is not enough room in the dealers repair area.", SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, NULL ); return(FALSE); // no room } */ if ( (ubPlayerOfferAreaSlotsNeeded + CountNumberOfItemsInThePlayersOfferArea( )) > (SKI_PLAYER_OFFER_BOXES_PER_ROW * SKI_PLAYER_OFFER_BOXES_PER_COL) ) { gfDisplayNoRoomMsg = TRUE; return(FALSE); // no room } // we have room, so move them all to the appropriate slots for ( size_t ubSubObject = 0; ubSubObject < subObjects.size(); ++ubSubObject ) { // if it's the main item itself (always in the very first subobject), and it has no other subobjects if ( ( ubSubObject == 0 ) && ( subObjects.size() == 1) ) { // store its owner merc as the owner, and store the correct slot fSuccess = AddObjectForEvaluation( &subObjects[ ubSubObject ], ubOwnerProfileId, bOwnerSlotId, fFirstOne ); } else // attachments, bullets/payload { // store it with a valid owner, but an invalid slot, so it still shows who owns it, but can't return to its slot // ARM: New code will be needed here if we add parent/child item support & interface fSuccess = AddObjectForEvaluation( &subObjects[ ubSubObject ], ubOwnerProfileId, -1, fFirstOne ); } // it has to succeed, or we have a bug in our earlier check for sufficient room Assert( fSuccess ); fFirstOne = FALSE; } gfAlreadySaidTooMuchToRepair = FALSE; } //ARM: This comment isn't true unless parent/child support is added. Right now repairmen don't do this! // NOTE that, either way, if owned, the item remains in the merc's inventory (hatched out) until the transaction is completed // Dirty fInterfacePanelDirty = DIRTYLEVEL2; return(TRUE); // it worked } void SplitComplexObjectIntoSubObjects( OBJECTTYPE *pComplexObject ) { Assert( pComplexObject ); Assert( pComplexObject->exists() == true ); subObjects.clear(); int usItem = pComplexObject->usItem; //CHRISL: This won't work. As we his MoveThisObjectTo, we reduce ubNumberOfObjects. If we started with ubNumberOfObjects=4 & x=0, // our first iteration would be ubNumberOfObjects=3 & x=1. Then ubNumberOfObjects=2 & x=2, which ends the loop. We want to // run the loop ubNumberOfObjects number of times regardless of whether ubNumberOfObjects is reduced. Use a while loop. //for (int x = 0; x < pComplexObject->ubNumberOfObjects; ++x) { while(pComplexObject->ubNumberOfObjects > 0) { //we need not worry about attachments!!!! if (pComplexObject->MoveThisObjectTo(gTempObject, 1) == 0) { subObjects.push_back(gTempObject); StackedObjectData* pData = subObjects.back()[0]; // if it's a gun if ( Item [ usItem ].usItemClass == IC_GUN ) { int usGunAmmoItem = pData->data.gun.usGunAmmoItem; int ubGunShotsLeft = pData->data.gun.ubGunShotsLeft; int bGunAmmoStatus = pData->data.gun.bGunAmmoStatus; // strip off any loaded ammo/payload // Exception: don't do this with rocket launchers, their "shots left" are fake and this screws 'em up! if ( !ItemIsSingleShotRocketLauncher(usItem)) // Madd rpg - still do this { pData->data.gun.usGunAmmoItem = NONE; pData->data.gun.ubGunShotsLeft = 0; pData->data.gun.ubGunState = 0; pData->data.gun.ubGunAmmoType = 0; pData->data.gun.bGunAmmoStatus = 0; } // and it has ammo/payload if ( usGunAmmoItem != NONE ) { // if it's bullets if ( Item[ usGunAmmoItem ].usItemClass == IC_AMMO ) { // and there are some left if ( ubGunShotsLeft > 0 ) { // make the bullets into another subobject CreateAmmo( usGunAmmoItem, &gTempObject, ubGunShotsLeft ); subObjects.push_back(gTempObject); } // ignore this if it's out of bullets } else // non-ammo payload { // if the gun was jammed, fix up the payload's status if ( bGunAmmoStatus < 0 ) { bGunAmmoStatus *= -1; } // make the payload into another subobject CreateItem( usGunAmmoItem, bGunAmmoStatus, &gTempObject ); subObjects.push_back(gTempObject); } } } } } } void CountSubObjectsInObject( OBJECTTYPE *pComplexObject, UINT8 *pubRepairableSubObjects, UINT8 *pubNonRepairableSubObjects ) { UINT8 ubSubObject; *pubRepairableSubObjects = 0; *pubNonRepairableSubObjects = 0; // check every subobject and count it as either repairable or non- for ( ubSubObject = 0; ubSubObject < subObjects.size(); ubSubObject++ ) { // is it in need of fixing, and also repairable by this dealer? // A jammed gun with a 100% status is NOT repairable - shouldn't ever happen if ( ( subObjects[ ubSubObject ][0]->data.objectStatus != 100 ) && CanDealerRepairItem( gbSelectedArmsDealerID, subObjects[ ubSubObject ].usItem ) ) { ( *pubRepairableSubObjects )++; } else { ( *pubNonRepairableSubObjects )++; } } } BOOLEAN AddObjectForEvaluation(OBJECTTYPE *pObject, UINT8 ubOwnerProfileId, INT16 bOwnerSlotId, BOOLEAN fFirstOne ) { INVENTORY_IN_SLOT InvSlot; INT8 bAddedToSlotID; // Make a new inv slot out of the subobject InvSlot.ItemObject = *pObject; InvSlot.sItemIndex = pObject->usItem; InvSlot.ubLocationOfObject = PLAYERS_INVENTORY; InvSlot.ubIdOfMercWhoOwnsTheItem = ubOwnerProfileId; //Add the item to the Players Offer Area bAddedToSlotID = AddItemToPlayersOfferArea( ubOwnerProfileId, &InvSlot, bOwnerSlotId ); //Do the evaluation for the item if it was added correctly if( bAddedToSlotID != -1 ) { // This will move any repairable (sub)objects into the dealer's offer area immediately EvaluateItemAddedToPlayersOfferArea( bAddedToSlotID, fFirstOne ); return( TRUE ); } else { return( FALSE ); } } // The Shopkeeper interface *MUST* use this intermediary function instead of calling AutoPlaceObject() directly! // This is because the OBJECTTYPEs used within Shopkeeper may contain an illegal ubNumberOfObjects BOOLEAN ShopkeeperAutoPlaceObject( SOLDIERTYPE * pSoldier, OBJECTTYPE * pObject, BOOLEAN fNewItem ) { // the entire pObj will get memset to 0 by RemoveObjs() if all the items are successfully placed, // so we have to keep a copy to retrieve with every iteration of the loop OBJECTTYPE movingObject; while ( pObject->exists() == true ) { // figure out how many to place during this loop iteration. Can't do more than MAX_OBJECTS_PER_SLOT at a time int numToMove = min( MAX_OBJECTS_PER_SLOT, pObject->ubNumberOfObjects); pObject->MoveThisObjectTo(movingObject, numToMove); if (!AutoPlaceObject( pSoldier, &movingObject, fNewItem )) //if (!AutoPlaceObject( pSoldier, pObject, fNewItem )) { //CHRISL: When we call this, we don't care about cap limits so stack up to max // no more room, didn't all fit - add back in any that we didn't even get to yet pObject->AddObjectsToStack(movingObject, -1, 0, NUM_INV_SLOTS, MAX_OBJECTS_PER_SLOT); return( FALSE ); } } return( TRUE ); } // The Shopkeeper interface *MUST* use this intermediary function instead of calling AddItemToPool() directly! // This is because the OBJECTTYPEs used within Shopkeeper may contain an illegal ubNumberOfObjects void ShopkeeperAddItemToPool( INT32 sGridNo, OBJECTTYPE *pObject, INT8 bVisible, UINT8 ubLevel, UINT16 usFlags, INT8 bRenderZHeightAboveLevel ) { // the entire pObj will get memset to 0 by RemoveObjs() if all the items are successfully placed, // so we have to keep a copy to retrieve with every iteration of the loop OBJECTTYPE movingObject; while ( pObject->exists() == true ) { // figure out how many to place during this loop iteration. Can't do more than MAX_OBJECTS_PER_SLOT at a time int numToMove = min( MAX_OBJECTS_PER_SLOT, pObject->ubNumberOfObjects); pObject->MoveThisObjectTo(movingObject, numToMove); AddItemToPool( sGridNo, &movingObject, bVisible, ubLevel, usFlags, bRenderZHeightAboveLevel ); } } void IfMercOwnedCopyItemToMercInv( INVENTORY_IN_SLOT *pInv ) { //if the item picked up was in a previous location, and that location is on a merc's inventory if ( ( pInv->bSlotIdInOtherLocation != -1 ) && ( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) ) { // get soldier SoldierID sSoldierID = GetSoldierIDFromMercID( pInv->ubIdOfMercWhoOwnsTheItem ); Assert( sSoldierID != NOBODY ); Assert( CanMercInteractWithSelectedShopkeeper( sSoldierID ) ); // then it better be a valid slot # Assert( pInv->bSlotIdInOtherLocation < (INT8)(sSoldierID->inv.size()) ); // and it better have a valid merc who owned it Assert( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ); //Copy the object back into that merc's original inventory slot sSoldierID->inv[ pInv->bSlotIdInOtherLocation ] = pInv->ItemObject; } } void IfMercOwnedRemoveItemFromMercInv( INVENTORY_IN_SLOT *pInv ) { IfMercOwnedRemoveItemFromMercInv2( pInv->ubIdOfMercWhoOwnsTheItem, pInv->bSlotIdInOtherLocation ); } void IfMercOwnedRemoveItemFromMercInv2( UINT8 ubOwnerProfileId, INT16 bOwnerSlotId ) { //if this item was in a previous location, and that location is on a merc's inventory if ( ( bOwnerSlotId != -1 ) && ( ubOwnerProfileId != NO_PROFILE ) ) { // and it better have a valid merc who owned it Assert( ubOwnerProfileId != NO_PROFILE ); SoldierID sSoldierID = GetSoldierIDFromMercID( ubOwnerProfileId ); Assert( sSoldierID != NOBODY ); // then it better be a valid slot # Assert( bOwnerSlotId < (INT8)(sSoldierID->inv.size() )); Assert( CanMercInteractWithSelectedShopkeeper( sSoldierID ) ); //remove the object from that merc's original inventory slot DeleteObj(&(sSoldierID->inv[bOwnerSlotId])); } } BOOLEAN SKITryToReturnInvToOwnerOrCurrentMerc( INVENTORY_IN_SLOT *pInv ) { // don't use this if the item has a copy in merc's inventory!! It would create a duplicate!! Assert( pInv->bSlotIdInOtherLocation == -1 ); // if it does have an owner if( pInv->ubIdOfMercWhoOwnsTheItem != NO_PROFILE ) { SoldierID sSoldierID = GetSoldierIDFromMercID( pInv->ubIdOfMercWhoOwnsTheItem ); // if that soldier is not in player's hire any longer if ( sSoldierID == -1 ) { return(FALSE); } // For owners of repaired items, this checks that owner is still hired, in sector, // on current squad, close enough to the shopkeeper, etc. if ( !CanMercInteractWithSelectedShopkeeper( sSoldierID ) ) { return(FALSE); } // Try to find a place to put in its owner's inventory (regardless of which merc is currently displayed!) if ( SKITryToAddInvToMercsInventory( pInv, sSoldierID ) ) { return( TRUE ); } // owner's inventory is full, so we'll try to give it to the current merc instead } //if the current merc is not disabled if( !gfSMDisableForItems ) { // Try to find a place to put in current merc's inventory if ( SKITryToAddInvToMercsInventory( pInv, gpSMCurrentMerc ) ) { return( TRUE ); } } //failed to add item, either inventory was filled up, or nobody was even eligible to receive it return( FALSE ); } BOOLEAN SKITryToAddInvToMercsInventory( INVENTORY_IN_SLOT *pInv, SOLDIERTYPE *pSoldier ) { INT8 bMoneyInvPos; BOOLEAN fNewItem = FALSE; //if the item is money if( Item[ pInv->sItemIndex ].usItemClass == IC_MONEY ) { // search through the merc's inventory for a pocket of money which isn't full already bMoneyInvPos = GetInvSlotOfUnfullMoneyInMercInventory( pSoldier ); // if he has a money pocket going if ( bMoneyInvPos != -1 ) { // try to add to it. If successful, it will delete the object... It returns TRUE even if not all would fit! PlaceObject( pSoldier, bMoneyInvPos, &( pInv->ItemObject ) ); // if the money is all gone if ( pInv->ItemObject[0]->data.money.uiMoneyAmount == 0 ) { // we've been succesful! return(TRUE); } // otherwise we'll try to place the rest seperately } } // If it's just been purchased or repaired, mark it as a "new item" fNewItem = ( BOOLEAN ) ( pInv->uiFlags & ( ARMS_INV_JUST_PURCHASED | ARMS_INV_ITEM_REPAIRED ) ); //try autoplacing the item in this soldier's inventory. if( !ShopkeeperAutoPlaceObject( pSoldier, &pInv->ItemObject, fNewItem ) ) { //failed to add item, inventory probably filled up return( FALSE ); } return(TRUE); } BOOLEAN CanMercInteractWithSelectedShopkeeper( SOLDIERTYPE *pSoldier ) { SOLDIERTYPE *pShopkeeper; INT32 sDestGridNo; INT8 bDestLevel; UINT32 uiRange; Assert( pSoldier!= NULL ); Assert( gbSelectedArmsDealerID != -1 ); if ( gusIDOfCivTrader != NOBODY ) pShopkeeper = MercPtrs[gusIDOfCivTrader]; else pShopkeeper = FindSoldierByProfileID( armsDealerInfo[gbSelectedArmsDealerID].ubShopKeeperID, FALSE ); Assert( pShopkeeper != NULL ); Assert( pShopkeeper->bActive ); Assert( pShopkeeper->bInSector ); if ( pShopkeeper->stats.bLife < OKLIFE ) { return( FALSE ); } if ( pSoldier->bActive && pSoldier->bInSector && IsMercOnCurrentSquad( pSoldier ) && ( pSoldier->stats.bLife >= OKLIFE ) && !( pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE ) && !AM_A_ROBOT( pSoldier ) ) { sDestGridNo = pShopkeeper->sGridNo; bDestLevel = pShopkeeper->pathing.bLevel; // If he has LOS... if ( SoldierTo3DLocationLineOfSightTest( pSoldier, sDestGridNo, bDestLevel, 3, TRUE, CALC_FROM_ALL_DIRS ) ) { // Get range to shopkeeper uiRange = PythSpacesAway( pSoldier->sGridNo, sDestGridNo ); // and is close enough to talk to the shopkeeper (use this define INSTEAD of PASSING_ITEM_DISTANCE_OKLIFE!) if ( uiRange <= NPC_TALK_RADIUS ) { return( TRUE ); } } } return( FALSE ); } #ifdef JA2TESTVERSION void AddShopkeeperToGridNo( UINT8 ubProfile, INT32 sGridNo ) { SOLDIERCREATE_STRUCT MercCreateStruct; INT16 sSectorX, sSectorY; UINT16 ubID; GetCurrentWorldSector( &sSectorX, &sSectorY ); MercCreateStruct.initialize(); MercCreateStruct.bTeam = CIV_TEAM; MercCreateStruct.ubProfile = ubProfile; MercCreateStruct.sSectorX = sSectorX; MercCreateStruct.sSectorY = sSectorY; MercCreateStruct.bSectorZ = gbWorldSectorZ; MercCreateStruct.sInsertionGridNo = sGridNo; if ( TacticalCreateSoldier( &MercCreateStruct, &ubID ) ) { AddSoldierToSector( ubID ); // So we can see them! AllTeamsLookForAll(NO_INTERRUPTS); } } #endif void ExitSKIRequested() { BOOLEAN fPlayerOwnedStuffOnTable = FALSE; ShutUpShopKeeper(); if( !gfRemindedPlayerToPickUpHisStuff ) { if( AreThereItemsInThePlayersOfferArea( ) ) { fPlayerOwnedStuffOnTable = TRUE; } if( ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) && AreThereItemsInTheArmsDealersOfferArea( ) ) { fPlayerOwnedStuffOnTable = TRUE; } //if there are any items belonging to player still on the table if( fPlayerOwnedStuffOnTable ) { // remind player to pick them up ShutUpShopKeeper( ); StartShopKeeperTalking( SK_QUOTES_PRESSED_DONE_STILL_HAS_STUFF_IN_OFFER_AREA ); gfRemindedPlayerToPickUpHisStuff = TRUE; return; } else { //if the player has already requested to leave if( gfUserHasRequestedToLeave ) { // has already asked to leave, shut the dealer up and allow the player to leave ShutUpShopKeeper(); return; } } } // if the player hasn't already requested to leave if( !gfUserHasRequestedToLeave ) { UINT16 usQuoteNum; if( gfDoneBusinessThisSession ) usQuoteNum = SK_QUOTES_PRESSES_DONE_HAS_AT_LEAST_1_TRANSACTION; else usQuoteNum = SK_QUOTES_PRESSED_DONE_HASNT_MADE_TRANSACTION; StartShopKeeperTalking( usQuoteNum ); //Set the fact that the player wants to leave gfUserHasRequestedToLeave = TRUE; } } void ResetAllQuoteSaidFlags() { // reset flags for quotes said for ( UINT8 uiCnt = 0; uiCnt < NUM_EVAL_RESULTS; ++uiCnt ) { gfEvalResultQuoteSaid[ uiCnt ] = FALSE; } guiLastTimeDealerSaidNormalEvaluationQuote = 0; } void DealWithItemsStillOnTheTable() { SOLDIERTYPE *pDropSoldier; // in case we have have to drop stuff off at someone's feet, figure out where it's all gonna go // use the current merc, unless he's ineligible, then use the selected merc instead. if( !gfSMDisableForItems ) { pDropSoldier = gpSMCurrentMerc; } else { pDropSoldier = gusSelectedSoldier; } // this guy HAS to be valid! Assert( CanMercInteractWithSelectedShopkeeper( pDropSoldier ) ); //loop through the players offer area and add return any items there to the player for ( int ubCnt = 0; ubCnt < (SKI_PLAYER_OFFER_BOXES_PER_ROW * SKI_PLAYER_OFFER_BOXES_PER_COL); ++ubCnt ) { //if there is an item here, give it back somehow if( PlayersOfferArea[ ubCnt ].fActive ) { ReturnItemToPlayerSomehow( &( PlayersOfferArea[ ubCnt ] ), pDropSoldier ); ClearPlayersOfferSlot( ubCnt ); } } //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { //loop through the arms dealers' offer area and return any items there to the player for ( int ubCnt = 0; ubCnt < (SKI_TRADER_OFFER_BOXES_PER_ROW * SKI_TRADER_OFFER_BOXES_PER_COL); ++ubCnt) { //if there is an item here, give it back somehow if( ArmsDealerOfferArea[ ubCnt ].fActive ) { ReturnItemToPlayerSomehow( &( ArmsDealerOfferArea[ ubCnt ] ), pDropSoldier ); ClearArmsDealerOfferSlot( ubCnt ); } } } } void ReturnItemToPlayerSomehow( INVENTORY_IN_SLOT *pInvSlot, SOLDIERTYPE *pDropSoldier ) { //if the item doesn't have a duplicate copy in its owner merc's inventory slot if( pInvSlot->bSlotIdInOtherLocation == -1 ) { // first try to give it to its owner, or if he's unavailable, the current merc if ( !SKITryToReturnInvToOwnerOrCurrentMerc( pInvSlot ) ) { // failed to add item, inventory probably filled up or item is unowned and current merc ineligible. // drop it at the specified guy's feet instead ShopkeeperAddItemToPool( pDropSoldier->sGridNo, &pInvSlot->ItemObject, VISIBLE, pDropSoldier->pathing.bLevel, 0, 0 ); } } //CHRISL: We need to handle LBENODE items a little differently so we don't lose anything stored in them. Instead of // trying to return the item, simply drop the item to the ground. if(UsingNewInventorySystem() == true && pInvSlot->ItemObject.IsActiveLBE(0) == true) { pDropSoldier->inv[pInvSlot->bSlotIdInOtherLocation].initialize(); ShopkeeperAddItemToPool( pDropSoldier->sGridNo, &pInvSlot->ItemObject, VISIBLE, pDropSoldier->pathing.bLevel, 0, 0 ); } } void GivePlayerSomeChange( UINT32 uiAmount ) { INVENTORY_IN_SLOT MoneyInvSlot; CreateMoney( uiAmount, &MoneyInvSlot.ItemObject ); MoneyInvSlot.sItemIndex = MoneyInvSlot.ItemObject.usItem; MoneyInvSlot.ubLocationOfObject = PLAYERS_INVENTORY; MoneyInvSlot.ubIdOfMercWhoOwnsTheItem = NO_PROFILE; AddItemToPlayersOfferArea( NO_PROFILE, &MoneyInvSlot, -1 ); if ( ( gbSelectedArmsDealerID == ARMS_DEALER_JAKE ) && ( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash < uiAmount ) ) { // HACK HACK HACK: We forgot to write/record quote 27 for Jake, so he ALWAYS must have enough money! gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash += ( uiAmount - gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash ); } // he must have enough cash left to give change Assert( gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash >= uiAmount ); // subtract change from dealer's cash gArmsDealerStatus[ gbSelectedArmsDealerID ].uiArmsDealersCash -= uiAmount; } void DealerGetsBribed( UINT8 ubProfileId, UINT32 uiMoneyAmount ) { BOOLEAN fBribable = FALSE; UINT32 uiMinBribe = 0; UINT16 usFact = 0; // this is only for dealers if (!IsMercADealer( ubProfileId ) ) { return; } switch ( ubProfileId ) { case FRANK: fBribable = TRUE; uiMinBribe = 10; // This should match the handling of a bribe submitted outside the SKI, using the GIVE system usFact = FACT_FRANK_HAS_BEEN_BRIBED; break; } // if this guy can be bribed, and we gave enough to bribe him if ( fBribable && ( uiMoneyAmount >= uiMinBribe ) ) { // set the appropriate fact SetFactTrue( usFact ); } } void HandlePossibleRepairDelays() { UINT32 uiHoursSinceAnyItemsShouldHaveBeenRepaired = 0; // assume there won't be a delay gfStartWithRepairsDelayedQuote = FALSE; // if it's Fredo or Perko #ifdef JA2UB if ( ( gbSelectedArmsDealerID == ARMS_DEALER_FREDO ) || ( gbSelectedArmsDealerID == ARMS_DEALER_RAUL ) ) #else if ( ( gbSelectedArmsDealerID == ARMS_DEALER_FREDO ) || ( gbSelectedArmsDealerID == ARMS_DEALER_PERKO ) ) #endif { // because the quotes are so specific, we'll only use them once per game per repairman if( !gArmsDealerStatus[ gbSelectedArmsDealerID ].fRepairDelayBeenUsed ) { // and it's been a while since the player last dealt with this repairman (within SKI that is) // this serves 2 purposes: // a) reduces delays being much more likely if player checks time remaining very frequently, AND // b) gives time for the events described in the text of the dealers' excuses to happen (e.g. scouting trip) if ( ( GetWorldTotalMin() - gArmsDealerStatus[ gbSelectedArmsDealerID ].uiTimePlayerLastInSKI ) >= ( 3 * 60 ) ) { // if he should have been finished, but it's only been a few hours since then (not days!) if ( RepairmanFixingAnyItemsThatShouldBeDoneNow( &uiHoursSinceAnyItemsShouldHaveBeenRepaired ) && ( uiHoursSinceAnyItemsShouldHaveBeenRepaired < REPAIR_DELAY_IN_HOURS ) ) { // then there's a fair chance he'll be delayed. Use pre-chance to hopefully preserve across reloads if ( PreChance( 50 ) ) { DelayRepairsInProgressBy( ( REPAIR_DELAY_IN_HOURS + Random( REPAIR_DELAY_IN_HOURS ) ) * 60 ); // this triggers a different opening quote gfStartWithRepairsDelayedQuote = TRUE; // set flag so it doesn't happen again gArmsDealerStatus[ gbSelectedArmsDealerID ].fRepairDelayBeenUsed = TRUE; } } } } } } BOOLEAN RepairmanFixingAnyItemsThatShouldBeDoneNow( UINT32 *puiHoursSinceOldestItemRepaired ) { //if the dealer is not a repair dealer, return if( !DoesDealerDoRepairs( gbSelectedArmsDealerID ) ) return( FALSE ); BOOLEAN fFoundOne = FALSE; UINT32 uiMinutesSinceItWasDone; UINT32 uiMinutesShopClosedSinceItWasDone; UINT32 uiWorkingHoursSinceThisItemRepaired; *puiHoursSinceOldestItemRepaired = 0; UINT32 currentTime = GetWorldTotalMin(); //loop through the dealers inventory and check if there are only unrepaired items for (DealerItemList::iterator iter = gArmsDealersInventory[gbSelectedArmsDealerID].begin(); iter != gArmsDealersInventory[gbSelectedArmsDealerID].end(); ++iter) { if ( iter->ItemIsInInventory() == true ) { if( iter->IsUnderRepair() == true ) { //if the repairs are done if( iter->uiRepairDoneTime <= currentTime ) { // at least one item is supposed to be done by now fFoundOne = TRUE; uiMinutesSinceItWasDone = currentTime - iter->uiRepairDoneTime; uiMinutesShopClosedSinceItWasDone = CalculateMinutesClosedBetween( gbSelectedArmsDealerID, iter->uiRepairDoneTime, currentTime ); // calculate how many WORKING hours since this item's been repaired uiWorkingHoursSinceThisItemRepaired = ( uiMinutesSinceItWasDone - uiMinutesShopClosedSinceItWasDone ) / 60; // we need to determine how long it's been since the item that's been repaired for the longest time was done if ( uiWorkingHoursSinceThisItemRepaired > *puiHoursSinceOldestItemRepaired ) { *puiHoursSinceOldestItemRepaired = uiWorkingHoursSinceThisItemRepaired; } } } } } // if FALSE returned here, he's either not repairing anything, or none of it's done yet return( fFoundOne ); } void DelayRepairsInProgressBy( UINT32 uiMinutesDelayed ) { UINT32 uiMinutesShopClosedBeforeItsDone; //if the dealer is not a repair dealer, return if( !DoesDealerDoRepairs( gbSelectedArmsDealerID ) ) return; //loop through the dealers inventory and check if there are only unrepaired items for (DealerItemList::iterator iter = gArmsDealersInventory[gbSelectedArmsDealerID].begin(); iter != gArmsDealersInventory[gbSelectedArmsDealerID].end(); ++iter) { if ( iter->ItemIsInInventory() == true ) { if( iter->IsUnderRepair() == true ) { uiMinutesShopClosedBeforeItsDone = CalculateOvernightRepairDelay( gbSelectedArmsDealerID, iter->uiRepairDoneTime, uiMinutesDelayed ); // add this many minutes to the repair time estimate iter->uiRepairDoneTime += ( uiMinutesShopClosedBeforeItsDone + uiMinutesDelayed ); } } } } //Mouse Call back for the Arms delaers face void SelectArmsDealersDropItemToGroundRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { // WANNE 2: not needed to drop the item to the ground->on the right side of the shopkeeper screen! /* if (iReason & MSYS_CALLBACK_REASON_INIT) { } else if(iReason & MSYS_CALLBACK_REASON_LBUTTON_UP) { SOLDIERTYPE *pDropSoldier; // use the current merc, unless he's ineligible, then use the selected merc instead. if( !gfSMDisableForItems ) { pDropSoldier = gpSMCurrentMerc; } else { pDropSoldier = gusSelectedSoldier; } //if we don't have an item, pick one up if( gMoveingItem.sItemIndex != 0 ) { //add the item to the ground ShopkeeperAddItemToPool( pDropSoldier->sGridNo, &gMoveingItem.ItemObject, VISIBLE, pDropSoldier->pathing.bLevel, 0, 0 ); //Reset the cursor SetSkiCursor( CURSOR_NORMAL ); //refresh the screen gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } } */ } void SelectArmsDealersDropItemToGroundMovementRegionCallBack(MOUSE_REGION * pRegion, INT32 iReason ) { if( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) { gfSkiDisplayDropItemToGroundText = FALSE; gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; } else if( iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) { if( CanTheDropItemToGroundStringBeDisplayed() ) { gfSkiDisplayDropItemToGroundText = TRUE; } else { gubSkiDirtyLevel = SKI_DIRTY_LEVEL2; gfSkiDisplayDropItemToGroundText = FALSE; } } else if( iReason & MSYS_CALLBACK_REASON_MOVE ) { } } BOOLEAN CanTheDropItemToGroundStringBeDisplayed() { //if we don't have an item, pick one up if( gMoveingItem.sItemIndex != 0 ) return( TRUE ); else return( FALSE ); } void DisplayTheSkiDropItemToGroundString() { DisplayWrappedString( SKI_DROP_ITEM_TO_GROUND_START_X, SKI_DROP_ITEM_TO_GROUND_TEXT_START_Y, (UINT16)(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, FALSE, CENTER_JUSTIFIED | DONT_DISPLAY_TEXT ); } UINT32 EvaluateInvSlot( INVENTORY_IN_SLOT *pInvSlot ) { UINT32 uiEvalResult = EVAL_RESULT_NORMAL; FLOAT dPriceModifier; UINT32 uiBuyingPrice; //if the dealer is Micky if( gbSelectedArmsDealerID == ARMS_DEALER_MICKY ) { SoldierID sSoldierID = GetSoldierIDFromMercID( armsDealerInfo[ gbSelectedArmsDealerID ].ubShopKeeperID ); if( ( sSoldierID != NOBODY ) && ( GetDrunkLevel( sSoldierID ) == DRUNK ) ) { //Micky is DRUNK, pays more! dPriceModifier = armsDealerInfo[ gbSelectedArmsDealerID ].dSellModifier; } else { // Micky isn't drunk, charge regular price dPriceModifier = armsDealerInfo[ gbSelectedArmsDealerID ].dBuyModifier; } } else { dPriceModifier = armsDealerInfo[ gbSelectedArmsDealerID ].dBuyModifier; } // Calculate dealer's buying price for the item uiBuyingPrice = CalcShopKeeperItemPrice( DEALER_BUYING, FALSE, pInvSlot->sItemIndex, dPriceModifier, &( pInvSlot->ItemObject ) ); pInvSlot->uiItemPrice = uiBuyingPrice; if ( uiBuyingPrice > 0 ) { // check if the item is really badly damaged if( Item[ pInvSlot->sItemIndex ].usItemClass != IC_AMMO ) { if( pInvSlot->ItemObject[0]->data.objectStatus < REALLY_BADLY_DAMAGED_THRESHOLD ) { uiEvalResult = EVAL_RESULT_OK_BUT_REALLY_DAMAGED; } } //the item can be sold here, so mark it as such pInvSlot->uiFlags |= ARMS_INV_PLAYERS_ITEM_HAS_VALUE; } else { // he normally buys this, but it's in such bad shape that he won't, it's worthless (different quote) uiEvalResult = EVAL_RESULT_WORTHLESS; pInvSlot->uiFlags &= ~ARMS_INV_PLAYERS_ITEM_HAS_VALUE; } return( uiEvalResult ); } void BuildRepairTimeString( CHAR16 sString[], UINT32 uiTimeInMinutesToFixItem ) { UINT16 usNumberOfHoursToFixItem = 0; // if it's 0, it shouldn't be up here any more! Assert ( uiTimeInMinutesToFixItem > 0 ); // round off to next higher 15 minutes if ( ( uiTimeInMinutesToFixItem % REPAIR_MINUTES_INTERVAL ) > 0 ) { uiTimeInMinutesToFixItem += REPAIR_MINUTES_INTERVAL - ( uiTimeInMinutesToFixItem % REPAIR_MINUTES_INTERVAL ); } if ( uiTimeInMinutesToFixItem < REPAIR_MINUTES_INTERVAL ) { uiTimeInMinutesToFixItem = REPAIR_MINUTES_INTERVAL; } // show up to 1.5 hrs in minutes if ( uiTimeInMinutesToFixItem <= 90 ) { // show minutes swprintf( sString, SKI_Text[ SKI_TEXT_MINUTES ], uiTimeInMinutesToFixItem ); } else { // show hours // round fractions of 15+ minutes up to next full hour usNumberOfHoursToFixItem = (UINT16) ( ( uiTimeInMinutesToFixItem + 45 ) / 60 ); if ( usNumberOfHoursToFixItem > 1 ) { swprintf( sString, SKI_Text[ SKI_TEXT_PLURAL_HOURS ], usNumberOfHoursToFixItem ); } else { wcscpy( sString, SKI_Text[ SKI_TEXT_ONE_HOUR ] ); } } } void BuildDoneWhenTimeString( CHAR16 sString[], UINT8 ubArmsDealer, INVENTORY_IN_SLOT* pObject ) { UINT32 uiDoneTime; UINT32 uiDay, uiHour, uiMin; //dealer must be a repair dealer Assert( DoesDealerDoRepairs( ubArmsDealer ) ); DEALER_SPECIAL_ITEM* pSpecial = 0; for (DealerItemList::iterator iter = gArmsDealersInventory[ubArmsDealer].begin(); iter != gArmsDealersInventory[ubArmsDealer].end(); ++iter) { if (iter->ItemIsInInventory() == true && iter->IsUnderRepair() == true) { if (pObject->ItemObject == iter->object && pObject->uiRepairDoneTime == iter->uiRepairDoneTime) { pSpecial = &(*iter); break; } } } Assert(pSpecial); // that item must be in repair Assert( pSpecial->IsUnderRepair() == true ); //if the item has already been repaired uiDoneTime = pSpecial->uiRepairDoneTime; if( uiDoneTime <= GetWorldTotalMin() ) { wcscpy( sString, L"" ); return; } // round off to next higher 15 minutes if ( ( uiDoneTime % REPAIR_MINUTES_INTERVAL ) > 0 ) { uiDoneTime += REPAIR_MINUTES_INTERVAL - ( uiDoneTime % REPAIR_MINUTES_INTERVAL ); } // uiDoneTime is in minutes... uiDay = ( uiDoneTime / NUM_MIN_IN_DAY ); uiHour = ( uiDoneTime - ( uiDay * NUM_MIN_IN_DAY ) ) / NUM_MIN_IN_HOUR; uiMin = uiDoneTime - ( ( uiDay * NUM_MIN_IN_DAY ) + ( uiHour * NUM_MIN_IN_HOUR ) ); // only show day if it's gonna take overnight if ( GetWorldDay() != uiDay ) { swprintf( sString, L"%s %d %02d:%02d", pDayStrings[ 0 ], uiDay, uiHour, uiMin ); } else { swprintf( sString, L"%02d:%02d", uiHour, uiMin ); } } void BuildItemHelpTextString( CHAR16 sString[], INVENTORY_IN_SLOT *pInv, UINT8 ubScreenArea ) { CHAR16 zHelpText[ 512 ]; CHAR16 zRepairTime[ 64 ]; if( pInv != NULL ) { GetHelpTextForItem( zHelpText, &( pInv->ItemObject ), NULL ); // add repair time for items in a repairman's offer area if ( ( ubScreenArea == ARMS_DEALER_OFFER_AREA ) && ( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) ) { BuildRepairTimeString( zRepairTime, CalculateObjectItemRepairTime( gbSelectedArmsDealerID, &( pInv->ItemObject ) ) ); swprintf( sString, L"%s\n(%s: %s)", zHelpText, gzLateLocalizedString[ 44 ], zRepairTime ); } else { wcscpy( sString, zHelpText ); } } else { sString[ 0 ] = '\0'; } } void DisableAllDealersInventorySlots( void ) { for ( int iCnt = 0; iCnt < gDealersInventoryActiveRegions; ++iCnt ) { MSYS_DisableRegion( &gDealersInventoryMouseRegions[ iCnt ] ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { MSYS_DisableRegion( &gRepairmanInventorySmallFaceMouseRegions[ iCnt ] ); } } } void EnableAllDealersInventorySlots( void ) { for ( int iCnt = 0; iCnt < gDealersInventoryActiveRegions; ++iCnt ) { MSYS_EnableRegion( &gDealersInventoryMouseRegions[ iCnt ] ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { MSYS_EnableRegion( &gRepairmanInventorySmallFaceMouseRegions[ iCnt ] ); } } } void DisableAllDealersOfferSlots( void ) { for ( int iCnt = 0; iCnt < gDealersOfferActiveRegions; ++iCnt ) { MSYS_DisableRegion( &gDealersOfferSlotsMouseRegions[ iCnt ] ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { MSYS_DisableRegion( &gDealersOfferSlotsSmallFaceMouseRegions[ iCnt ] ); } } } void EnableAllDealersOfferSlots( void ) { for ( int iCnt = 0; iCnt < gDealersOfferActiveRegions; ++iCnt ) { MSYS_EnableRegion( &gDealersOfferSlotsMouseRegions[ iCnt ] ); //if the dealer repairs if( armsDealerInfo[ gbSelectedArmsDealerID ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS ) { MSYS_EnableRegion( &gDealersOfferSlotsSmallFaceMouseRegions[ iCnt ] ); } } } void HatchOutInvSlot( UINT16 usPosX, UINT16 usPosY ) { UINT16 usSlotX, usSlotY; UINT16 usSlotWidth, usSlotHeight; //if we are in the item desc panel if( InItemDescriptionBox( ) && pShopKeeperItemDescObject != NULL ) { // do nothing, don't wanna shade things twice! return; } usSlotX = ( UINT16 ) ( usPosX - 1 ); usSlotY = ( UINT16 ) ( usPosY - 1 ); usSlotWidth = SKI_INV_SLOT_WIDTH; usSlotHeight = SKI_INV_SLOT_HEIGHT; //Hatch it out DrawHatchOnInventory( guiRENDERBUFFER, usSlotX, usSlotY, usSlotWidth, usSlotHeight ); InvalidateRegion( usSlotX, usSlotY, usSlotX + usSlotWidth, usSlotY + usSlotHeight ); } #ifdef JA2UB //ja25 UB void AskUserToAttachTransmitterToLaptop() { DoSkiMessageBox( MSG_BOX_BASIC_STYLE, zNewTacticalMessages[ TCTL_MSG__ATTACH_TRANSMITTER_TO_LAPTOP ], SHOPKEEPER_SCREEN, MSG_BOX_FLAG_OK, AttachLaptopTransmitterToLaptop ); } void AttachLaptopTransmitterToLaptop( UINT8 ubExitValue ) { // // Get the laptop working again // //Mark the quest done if ( gGameUBOptions.LaptopQuestEnabled == TRUE ) EndQuest( QUEST_FIX_LAPTOP, -1, -1 ); } #endif