diff --git a/GameSettings.cpp b/GameSettings.cpp index 91925d86..88e22ed9 100644 --- a/GameSettings.cpp +++ b/GameSettings.cpp @@ -2257,13 +2257,15 @@ void LoadCTHConstants() //Kaiden: Setting Ja2_Options.ini file to be read CIniReader iniReader(CTH_COEFFICIENTS_FILE); - gGameCTHConstants.NORMAL_SHOOTING_DISTANCE = iniReader.ReadInteger("General","NORMAL_SHOOTING_DISTANCE",100, 10, 10000); + gGameCTHConstants.NORMAL_SHOOTING_DISTANCE = iniReader.ReadInteger("General","NORMAL_SHOOTING_DISTANCE", 70, 10, 10000); gGameCTHConstants.DEGREES_MAXIMUM_APERTURE = iniReader.ReadFloat("General", "DEGREES_MAXIMUM_APERTURE", 15.0, 0.0, 22.5); gGameCTHConstants.RANGE_COEFFICIENT = iniReader.ReadFloat("General", "RANGE_COEFFICIENT", 2.0, 0.001f, 100.0); gGameCTHConstants.GRAVITY_COEFFICIENT = iniReader.ReadFloat("General", "GRAVITY_COEFFICIENT", 1.0, 0.001f, 100.0); gGameCTHConstants.VERTICAL_BIAS = iniReader.ReadFloat("General", "VERTICAL_BIAS", 1.0f, 0.01f, 2.0f); gGameCTHConstants.SCOPE_RANGE_MULTIPLIER = iniReader.ReadFloat("General", "SCOPE_RANGE_MULTIPLIER", 0.7f, 0.5f, 1.5f); gGameCTHConstants.SIDE_FACING_DIVISOR = iniReader.ReadFloat("General", "SIDE_FACING_DIVISOR", 2.0, 1.0f, 10.0f); + // HEADROCK HAM 5: Basic chance to lose condition point when firing + gGameCTHConstants.BASIC_RELIABILITY_ODDS = iniReader.ReadInteger("General", "BASIC_RELIABILITY_ODDS", 15, 0, 100); //////////////////////////////////////////////////////////// // Coefficients for factors affecting Base CTH @@ -2370,7 +2372,7 @@ void LoadCTHConstants() gGameCTHConstants.RECOIL_MAX_COUNTER_FORCE = iniReader.ReadFloat("Shooting Mechanism","RECOIL_MAX_COUNTER_FORCE",5.0, 0.0, 100.0); gGameCTHConstants.RECOIL_MAX_COUNTER_CROUCH = iniReader.ReadFloat("Shooting Mechanism","RECOIL_MAX_COUNTER_CROUCH",10.0, -100.0, 100.0); gGameCTHConstants.RECOIL_MAX_COUNTER_PRONE = iniReader.ReadFloat("Shooting Mechanism","RECOIL_MAX_COUNTER_PRONE",25.0, -100.0, 100.0); - gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR = iniReader.ReadFloat("Shooting Mechanism","RECOIL_COUNTER_ACCURACY_MIN",0.2f, 0.0, 100.0); + gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR = iniReader.ReadFloat("Shooting Mechanism","RECOIL_COUNTER_ACCURACY_MIN_ERROR",0.2f, 0.0, 100.0); gGameCTHConstants.RECOIL_COUNTER_ACCURACY_DEX = iniReader.ReadFloat("Shooting Mechanism","RECOIL_COUNTER_ACCURACY_DEX",3.0, 0.0, 100.0); gGameCTHConstants.RECOIL_COUNTER_ACCURACY_WIS = iniReader.ReadFloat("Shooting Mechanism","RECOIL_COUNTER_ACCURACY_WIS",1.0, 0.0, 100.0); gGameCTHConstants.RECOIL_COUNTER_ACCURACY_AGI = iniReader.ReadFloat("Shooting Mechanism","RECOIL_COUNTER_ACCURACY_AGI",1.0, 0.0, 100.0); diff --git a/GameSettings.h b/GameSettings.h index 64fc5bbe..899de3e7 100644 --- a/GameSettings.h +++ b/GameSettings.h @@ -1345,6 +1345,7 @@ typedef struct FLOAT VERTICAL_BIAS; // This float can be used to reduce the chance of missing too far upwards or downwards (compared to left/right). FLOAT SCOPE_RANGE_MULTIPLIER; // Adjusts the minimum effective range of scopes FLOAT SIDE_FACING_DIVISOR; // Deals with a visual error in NCTH relating to shooting at a target who is facing directly perpendicular to the shooters facing. + UINT16 BASIC_RELIABILITY_ODDS; // Determines the base chance to lose one condition point when firing a gun. FLOAT BASE_EXP; // Importance of Experience for BASE CTH FLOAT BASE_MARKS; // Importance of Marksmanship for BASE CTH diff --git a/Init.cpp b/Init.cpp index 20a92783..5b1c960a 100644 --- a/Init.cpp +++ b/Init.cpp @@ -367,6 +367,11 @@ BOOLEAN LoadExternalGameplayData(STR directoryName) strcat(fileName, ATTACHMENTCOMBOMERGESFILENAME); SGP_THROW_IFFALSE(ReadInAttachmentComboMergeStats(fileName),ATTACHMENTCOMBOMERGESFILENAME); + // HEADROCK HAM 5: Read item transformation + strcpy(fileName, directoryName); + strcat(fileName, ITEMTRANSFORMATIONSFILENAME); + SGP_THROW_IFFALSE(ReadInTransformationStats(fileName),ITEMTRANSFORMATIONSFILENAME); + strcpy(fileName, directoryName); strcat(fileName, EXPLOSIVESFILENAME); SGP_THROW_IFFALSE(ReadInExplosiveStats(fileName),EXPLOSIVESFILENAME); @@ -386,6 +391,12 @@ BOOLEAN LoadExternalGameplayData(STR directoryName) strcat(fileName, LBEPOCKETFILENAME); SGP_THROW_IFFALSE(ReadInLBEPocketStats(fileName,FALSE),LBEPOCKETFILENAME); + // THE_BOB : added for pocket popup definitions + LBEPocketPopup.clear(); + strcpy(fileName, directoryName); + strcat(fileName, LBEPOCKETPOPUPFILENAME); + SGP_THROW_IFFALSE(ReadInLBEPocketPopups(fileName),LBEPOCKETPOPUPFILENAME); + //CHRISL: Simple localization // Same setup as what Madd used for items.xml @@ -643,6 +654,13 @@ BOOLEAN LoadExternalGameplayData(STR directoryName) strcat(fileName, SECTORNAMESFILENAME); DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); SGP_THROW_IFFALSE(ReadInSectorNames(fileName,FALSE,0), SECTORNAMESFILENAME); + + // HEADROCK HAM 5: Read in Coolness by Sector + strcpy(fileName, directoryName); + strcat(fileName, COOLNESSBYSECTORFILENAME); + DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("LoadExternalGameplayData, fileName = %s", fileName)); + SGP_THROW_IFFALSE(ReadInCoolnessBySector(fileName), COOLNESSBYSECTORFILENAME); + #ifndef ENGLISH AddLanguagePrefix(fileName); if ( FileExists(fileName) ) diff --git a/SaveLoadGame.cpp b/SaveLoadGame.cpp index 3a174ea6..7886a3b9 100644 --- a/SaveLoadGame.cpp +++ b/SaveLoadGame.cpp @@ -5714,7 +5714,8 @@ BOOLEAN LoadSavedGame( int ubSavedGameID ) // HEADROCK HAM B1: Re-Adjust Dynamic Roaming Militia restrictions if (gGameExternalOptions.fDynamicRestrictRoaming) { - AdjustRoamingRestrictions(); + // HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. + AdjustRoamingRestrictions( TRUE ); } // HEADROCK HAM 3.5: Tells the rest of the program whether we've got mercs working on detecting enemy units. diff --git a/Strategic/Auto Resolve.cpp b/Strategic/Auto Resolve.cpp index c9bbeeca..96445b38 100644 --- a/Strategic/Auto Resolve.cpp +++ b/Strategic/Auto Resolve.cpp @@ -68,6 +68,7 @@ // #include "Strategic AI.h" #include "interface Dialogue.h" #include "AIInternals.h" // added by SANDRO + #include "Bullets.h" // HEADROCK HAM 5, for use with Bullet Impact. #endif #include "Reinforcement.h" @@ -4414,7 +4415,8 @@ void AttackTarget( SOLDIERCELL *pAttacker, SOLDIERCELL *pTarget ) else ubLocation = AIM_SHOT_TORSO; ubAccuracy = (UINT8)((usAttack - usDefence + PreRandom( usDefence - pTarget->usDefence ) )/10); - iImpact = BulletImpact( pAttacker->pSoldier, pTarget->pSoldier, ubLocation, ubImpact, ubAccuracy, NULL ); + // HEADROCK HAM 5: Added argument + iImpact = BulletImpact( pAttacker->pSoldier, NULL, pTarget->pSoldier, ubLocation, ubImpact, ubAccuracy, NULL ); ///////////////////////////////////////////////////////////////////////////////////////////////////////////// // SANDRO - increased mercs' offense/deffense rating if (pAttacker->uiFlags & CELL_MERC && gGameExternalOptions.sMercsAutoresolveOffenseBonus != 0) diff --git a/Strategic/Map Screen Interface Border.cpp b/Strategic/Map Screen Interface Border.cpp index 5b336167..edecaad5 100644 --- a/Strategic/Map Screen Interface Border.cpp +++ b/Strategic/Map Screen Interface Border.cpp @@ -28,8 +28,12 @@ // Also include Town Militia for checks regarding Mobile Militia Restrictions #include "Town Militia.h" // Also include Quests, for checking whether a fact is true. - #include "Quests.h" + #include "Quests.h" + // HEADROCK HAM 5: Required for inventory filter popup + #include "popup_callback.h" + #include "popup_class.h" #endif + #include "connect.h" #ifdef JA2UB @@ -91,6 +95,13 @@ BOOLEAN fZoomFlag = FALSE; //BOOLEAN fCursorIsOnMapScrollButtons = FALSE; //BOOLEAN fDisabledMapBorder = FALSE; +// HEADROCK HAM 5: Externed here to be able to forgo redrawing the map inventory. +extern POPUP* gMapInventoryFilterPopup; +extern BOOLEAN gfMapInventoryFilterPopupVisible; +extern BOOLEAN gfQueueRecreateMapInventoryFilterMenu; +extern UINT32 guiMapInvenFilterButton[ 9 ]; +extern void CreateMapInventoryFilterMenu( ); + // buttons & button images // HEADROCK HAM 4: Increase both arrays by one to accomodate new Mobile Restrictions button @@ -210,17 +221,29 @@ void RenderMapBorder( void ) } */ + // HEADROCK HAM 5: Do not redraw if the filter menu is open. if( fShowMapInventoryPool ) { - // render background, then leave - BlitInventoryPoolGraphic( ); + if (gfMapInventoryFilterPopupVisible) + { + if (gfQueueRecreateMapInventoryFilterMenu) + { + ButtonList[guiMapInvenFilterButton[ 0 ]]->uiFlags |= (BUTTON_CLICKED_ON); + CreateMapInventoryFilterMenu(); + } + gMapInventoryFilterPopup->show(); + } + else + { + // render background, then leave + BlitInventoryPoolGraphic( ); + } return; } // get and blt border GetVideoObject(&hHandle, guiMapBorder ); // HEADROCK HAM 4: Load different map border depending on whether we want to display the mobile militia button or not. - if (gGameExternalOptions.gfAllowMilitiaGroups) { BltVideoObject( guiSAVEBUFFER , hHandle, 1, xResOffset + MAP_BORDER_X, MAP_BORDER_Y, VO_BLT_SRCTRANSPARENCY,NULL ); diff --git a/Strategic/Map Screen Interface Map Inventory.cpp b/Strategic/Map Screen Interface Map Inventory.cpp index 41922b4d..5288b502 100644 --- a/Strategic/Map Screen Interface Map Inventory.cpp +++ b/Strategic/Map Screen Interface Map Inventory.cpp @@ -79,6 +79,8 @@ extern BOOLEAN SaveWorldItemsToTempItemFile( INT16 sMapX, INT16 sMapY, INT8 bMap // inventory slot font #define MAP_IVEN_FONT SMALLCOMPFONT +// HEADROCK HAM 5: Item names use variable fonts. +INT32 MAP_INVEN_NAME_FONT = SMALLCOMPFONT; // the position of the background graphic #define INVEN_POOL_X 261 @@ -89,20 +91,26 @@ extern BOOLEAN SaveWorldItemsToTempItemFile( INT16 sMapX, INT16 sMapY, INT8 bMap #define MAP_INVENTORY_POOL_SLOT_OFFSET_Y 5 // height of map inventory pool bar -#define ITEMDESC_ITEM_STATUS_HEIGHT_INV_POOL 20 -#define ITEMDESC_ITEM_STATUS_WIDTH_INV_POOL 2 +// HEADROCK HAM 5: Changed to variables +UINT16 ITEMDESC_ITEM_STATUS_HEIGHT_INV_POOL = 20; +UINT16 ITEMDESC_ITEM_STATUS_WIDTH_INV_POOL = 2; // map bar offsets -#define ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_X 5 -#define ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y 22 +// HEADROCK HAM 5: Changed to variables +INT16 ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_X = 5; +INT16 ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y = 22; + +// HEADROCK HAM 5: For now I just need a Y. +INT16 ITEMDESC_ITEM_NAME_POOL_OFFSET_Y = 3; // inventory pool slot positions and sizes //#define MAP_INVENTORY_POOL_SLOT_START_X 271 //#define MAP_INVENTORY_POOL_SLOT_START_Y 36 -#define MAP_INVEN_SLOT_WIDTH 65 -#define MAP_INVEN_SPACE_BTWN_SLOTS 72 -#define MAP_INVEN_SLOT_HEIGHT 32 -#define MAP_INVEN_SLOT_IMAGE_HEIGHT 24 +// HEADROCK HAM 5: Changed to variables +UINT16 MAP_INVEN_SLOT_WIDTH = 65; +UINT16 MAP_INVEN_SPACE_BTWN_SLOTS = 72; +UINT16 MAP_INVEN_SLOT_HEIGHT = 32; +UINT16 MAP_INVEN_SLOT_IMAGE_HEIGHT = 24; // Number of inventory slots in 1024x768 #define MAP_INVENTORY_POOL_MAX_SLOTS 170 @@ -119,9 +127,14 @@ BOOLEAN fFlashHighLightInventoryItemOnradarMap = FALSE; // whether we are showing the inventory pool graphic BOOLEAN fShowMapInventoryPool = FALSE; +// HEADROCK HAM 5: Flag telling us whether we've already redrawn the screen to show +// sector inventory sale prices. +UINT8 gubRenderedMapInventorySalePrices = FALSE; // the v-object index value for the background UINT32 guiMapInventoryPoolBackground; +// HEADROCK HAM 5: Graphic for map inventory slots +UINT32 guiMapInventoryPoolSlot; // inventory pool list std::vector pInventoryPoolList; @@ -140,10 +153,14 @@ UINT32 uiNumberOfUnSeenItems = 0; // the inventory slots //MOUSE_REGION MapInventoryPoolSlots[ MAP_INVENTORY_POOL_SLOT_COUNT ]; MOUSE_REGION MapInventoryPoolSlots[ MAP_INVENTORY_POOL_MAX_SLOTS ]; - MOUSE_REGION MapInventoryPoolMask; +// HEADROCK HAM 5: This rectangle describes the inventory. It is used to limit cursor movement while waiting for zoom input. +SGPRect MapInventoryRect; + //BOOLEAN fMapInventoryItemCompatable[ MAP_INVENTORY_POOL_SLOT_COUNT ]; BOOLEAN fMapInventoryItemCompatable[ MAP_INVENTORY_POOL_MAX_SLOTS ]; +// HEADROCK HAM 5: Same idea as above, this flags items as being candidates for appearing in Zoomed mode. +BOOLEAN gfMapInventoryItemToZoom[ MAP_INVENTORY_POOL_MAX_SLOTS ]; BOOLEAN fChangedInventorySlots = FALSE; @@ -157,11 +174,45 @@ INT32 giFlashHighlightedItemBaseTime = 0; INT32 giCompatibleItemBaseTime = 0; // the buttons and images -UINT32 guiMapInvenButtonImage[ 3 ]; -UINT32 guiMapInvenButton[ 3 ]; +// HEADROCK HAM 5: Increased for new inventory buttons +UINT32 guiMapInvenArrowButtonImage[ 2 ]; +UINT32 guiMapInvenArrowButton[ 2 ]; + +UINT32 guiMapInvenDoneButtonImage; +UINT32 guiMapInvenDoneButton; + +UINT32 guiMapInvenZoomButtonImage; +UINT32 guiMapInvenZoomButton; + +UINT32 guiMapInvenSortButtonImage[4]; +UINT32 guiMapInvenSortButton[4]; + +UINT32 guiMapInvenFilterButtonImage[9]; +UINT32 guiMapInvenFilterButton[9]; + + BOOLEAN gfCheckForCursorOverMapSectorInventoryItem = FALSE; +// HEADROCK HAM 5: Map Inventory ZOOM +BOOLEAN fMapInventoryZoom = FALSE; +// HEADROCK HAM 5: This flag tells us that we're waiting for player zoom region selection. It affects the +// behavior of inventory slot regions. +BOOLEAN fWaitingForZoomInput = FALSE; + +// HEADROCK HAM 5: This is the minimum desired number of inventory items. Unless we reset this to -1, there will be +// exactly as many item pages as we had previously when checking inventory resize. +INT32 giDesiredNumMapInventorySlots = -1; + +// HEADROCK HAM 5: This is an inventory filter. It is a bit array, reacting to an item's Class. +UINT32 guiMapInventoryFilter = IC_MAPFILTER_ALL; + +// HEADROCK HAM 5: Constants and variables for the filter popup menu. +BOOLEAN gfMapInventoryFilterPopupInitialized = FALSE; +POPUP* gMapInventoryFilterPopup; +BOOLEAN gfMapInventoryFilterPopupVisible = FALSE; +BOOLEAN gfQueueRecreateMapInventoryFilterMenu = FALSE; + extern int PLAYER_INFO_Y; extern int INV_REGION_Y; @@ -180,7 +231,15 @@ extern UINT16 gusExternVoSubIndex; extern MOUSE_REGION gMPanelRegion; +// HEADROCK HAM 5: Because BigItem graphics are not loaded into memory by default, we need to load them to +// display the large map inventory. We save their indexes in this array: +typedef struct +{ + UINT16 usItem; + INT32 iGraphicNum; +} BIGITEMSLOTGRAPHICS; +BIGITEMSLOTGRAPHICS giMapInventoryBigItemGraphics[ MAP_INVENTORY_POOL_MAX_SLOTS ]; // map inventory callback void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ); @@ -203,6 +262,15 @@ void DrawNumberOfIventoryPoolItems( void ); void CreateMapInventoryPoolDoneButton( void ); void DestroyInventoryPoolDoneButton( void ); void MapInventoryPoolDoneBtn( GUI_BUTTON *btn, INT32 reason ); +// HEADROCK HAM 5: Callback for inventory zoom +void MapInventoryPoolZoomBtn( GUI_BUTTON *btn, INT32 reason ); +// HEADROCK HAM 5: Sort ammo button callback +void MapInventoryPoolStackAndMergeBtn( GUI_BUTTON *btn, INT32 reason ); +void MapInventoryPoolSortAmmoBtn( GUI_BUTTON *btn, INT32 reason ); +void MapInventoryPoolSortAttachmentsBtn( GUI_BUTTON *btn, INT32 reason ); +void MapInventoryPoolEjectAmmoBtn( GUI_BUTTON *btn, INT32 reason ); +// HEADROCK HAM 5: Preliminary Filter Button +void MapInventoryPoolFilterBtn( GUI_BUTTON *btn, INT32 reason ); void DisplayCurrentSector( void ); void ResizeInventoryList( void ); void ClearUpTempUnSeenList( void ); @@ -226,7 +294,42 @@ extern BOOLEAN MAPInternalInitItemDescriptionBox( OBJECTTYPE *pObject, UINT8 ubS void DeleteAllItemsInInventoryPool(); void DeleteItemsOfType( UINT16 usItemType ); -INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier = TRUE ); +// HEADROCK HAM 5: flag to indicate that all items in the stack will be sold. This is primarily used to +// figure out the price of the top item in the stack rather than just all of them. +INT32 SellItem( OBJECTTYPE& object, BOOLEAN fAll, BOOLEAN useModifier = TRUE ); + +// HEADROCK HAM 5: Resets the coordinates and sizes of inventory slots. +void ResetMapInventoryOffsets(void); +// HEADROCK HAM 5: Handles loading and unloading Big Item images. +void LoadAllMapInventoryBigItemGraphics(); +void LoadMapInventoryBigItemGraphic( INT32 iCounter ); +void UnloadAllMapInventoryBigItemGraphics(); +void UnloadMapInventoryBigItemGraphic( INT32 iCounter ); +void ResetAllMapInventoryBigItemGraphics(); +// HEADROCK HAM 5: Handles map inventory zoom/unzoom to specific page. +void HandleMapInventoryZoom( UINT32 iPage, INT32 iCounter ); +void HandleMapInventoryUnzoom( UINT32 iPage ); +// HEADROCK HAM 5: Function to get the number of slots when zoomed. +UINT16 GetInventorySlotCount( BOOLEAN fZoomed ); +void CancelInventoryZoomInput( BOOLEAN fButtonOff ); + +void AnimateZoomInventory ( UINT16 iLocationInPool, UINT16 iCounter, INT32 iStartX, INT32 iStartY, UINT32 uiOrigWidth, UINT32 uiOrigHeight ); +// HEADROCK HAM 5: This does the same thing as CTRL-SHIFT-A in the tactical view, except it sorts +// the currently-opened sector inventory ammo. +void SortSectorInventoryAmmo(bool useBoxes); +void SortSectorInventoryEjectAmmo(); +void SortSectorInventorySeparateAttachments(); +void SortSectorInventoryStackAndMerge(bool ammoOnly); +// HEADROCK HAM 5: A quick function to rebuilt the Seen and Unseen item pools without saving the inventory. +void RefreshSeenAndUnseenPools(); +void CreateMapInventoryFilterMenu( ); +void MapInventoryFilterMenuPopup_FilterToggle( UINT32 uiFlags ); +void MapInventoryFilterMenuPopup_FilterSet(UINT32 uiFlags ); +void MapInventoryFilterMenuPopup_Hide( void ); +BOOLEAN MapInventoryFilterMenuPopup_OptionOff( void ); +void MapInventoryFilterToggle( UINT32 uiFlags ); +void MapInventoryFilterSet( UINT32 uiFlags ); +void HandleSetFilterButtons(); // load the background panel graphics for inventory BOOLEAN LoadInventoryPoolGraphic( void ) @@ -264,6 +367,14 @@ BOOLEAN LoadInventoryPoolGraphic( void ) // add to V-object index CHECKF(AddVideoObject(&VObjectDesc, &guiMapInventoryPoolBackground)); + // HEADROCK HAM 5: Load individual slot pics. + sprintf( VObjectDesc.ImageFile, "INTERFACE\\sector_inventory_slot.sti" ); + CHECKF(AddVideoObject(&VObjectDesc, &guiMapInventoryPoolSlot)); + + // HEADROCK HAM 5: Take this opportunity to load BigItem asterisks. + sprintf( VObjectDesc.ImageFile, "INTERFACE\\Attachment_Asterisks.sti" ); + CHECKF(AddVideoObject(&VObjectDesc, &guiAttachmentAsterisks)); + return( TRUE ); } @@ -279,6 +390,20 @@ void RemoveInventoryPoolGraphic( void ) guiMapInventoryPoolBackground = 0; } + // HEADROCK HAM 5: Remove slot image + if (guiMapInventoryPoolSlot) + { + DeleteVideoObjectFromIndex( guiMapInventoryPoolSlot ); + guiMapInventoryPoolSlot = 0; + } + + // HEADROCK HAM 5: Remove asterisks + if (guiAttachmentAsterisks) + { + DeleteVideoObjectFromIndex( guiAttachmentAsterisks ); + guiAttachmentAsterisks = 0; + } + return; } @@ -291,6 +416,9 @@ void BlitInventoryPoolGraphic( void ) GetVideoObject(&hHandle, guiMapInventoryPoolBackground); BltVideoObject( guiSAVEBUFFER , hHandle, 0,(SCREEN_WIDTH - INTERFACE_WIDTH)/2 + INVEN_POOL_X, INVEN_POOL_Y , VO_BLT_SRCTRANSPARENCY,NULL ); + // HEADROCK HAM 5: Draw inventory slots + BlitInventoryPoolSlotGraphics(); + // resize list ResizeInventoryList( ); @@ -324,6 +452,26 @@ void BlitInventoryPoolGraphic( void ) return; } +// HEADROCK HAM 5: Blit all map inventory item slots +void BlitInventoryPoolSlotGraphics( void ) +{ + INT16 sX, sY; + HVOBJECT hHandle; + + // blit inventory pool graphic to the screen + GetVideoObject(&hHandle, guiMapInventoryPoolSlot); + + for( INT32 iCounter = 0; iCounter < MAP_INVENTORY_POOL_SLOT_COUNT ; iCounter++ ) + { + sX = ( INT16 )( MAP_INVENTORY_POOL_SLOT_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCounter / MAP_INV_SLOT_COLS ) ) ); + sY = ( INT16 )( MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCounter % ( MAP_INV_SLOT_COLS ) ) ) ); + + BltVideoObject( guiSAVEBUFFER , hHandle, fMapInventoryZoom, sX, sY , VO_BLT_SRCTRANSPARENCY,NULL ); + } + + return; +} + void RenderItemsForCurrentPageOfInventoryPool( void ) { INT32 iCounter = 0; @@ -353,7 +501,8 @@ BOOLEAN RenderItemInPoolSlot( INT32 iCurrentSlot, INT32 iFirstSlotOnPage ) if( pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.exists() == false ) { - //return ( FALSE ); + // HEADROCK HAM 5: Have to return false. + return ( FALSE ); } GetVideoObject( &hHandle, GetInterfaceGraphicForItem( &(Item[ pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.usItem ] ) ) ); @@ -372,15 +521,23 @@ BOOLEAN RenderItemInPoolSlot( INT32 iCurrentSlot, INT32 iFirstSlotOnPage ) sCenY = sY + ( abs( MAP_INVEN_SLOT_HEIGHT - 5 - usHeight ) / 2 ) - pTrav->sOffsetY; - if( fMapInventoryItemCompatable[ iCurrentSlot ] ) + if( fMapInventoryItemCompatable[ iCurrentSlot ] || gfMapInventoryItemToZoom[ iCurrentSlot ] ) { - sOutLine = Get16BPPColor( FROMRGB( 255, 255, 255 ) ); - fOutLine = TRUE; + fOutLine = TRUE; + // Determine Color + if ( fMapInventoryItemCompatable[ iCurrentSlot ] ) + { + sOutLine = Get16BPPColor( FROMRGB( 255, 255, 255 ) ); + } + else if ( gfMapInventoryItemToZoom[ iCurrentSlot ] ) + { + sOutLine = Get16BPPColor( FROMRGB( 255, 255, 55 ) ); + } } else { sOutLine = us16BPPItemCyclePlacedItemColors[ 0 ]; - fOutLine = FALSE; + fOutLine = FALSE; } SetFontDestBuffer( guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); @@ -391,8 +548,27 @@ BOOLEAN RenderItemInPoolSlot( INT32 iCurrentSlot, INT32 iFirstSlotOnPage ) RenderPocketItemCapacity( guiSAVEBUFFER, itemSlotLimit, STACK_SIZE_LIMIT, 0, &pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object, (sX + 7), sY ); } - INVRenderItem( guiSAVEBUFFER, NULL, &(pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object), + if (fMapInventoryZoom) + { + // Check whether the correct graphic exists. + if (giMapInventoryBigItemGraphics[ iCurrentSlot ].iGraphicNum == -1 || + giMapInventoryBigItemGraphics[ iCurrentSlot ].usItem != pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.usItem ) + { + UnloadMapInventoryBigItemGraphic( iCurrentSlot ); + LoadMapInventoryBigItemGraphic( iCurrentSlot ); + } + // Retest + if (giMapInventoryBigItemGraphics[ iCurrentSlot ].usItem > 0 && giMapInventoryBigItemGraphics[ iCurrentSlot ].iGraphicNum > -1) + { + MAPINVRenderItem( guiSAVEBUFFER, NULL, &(pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object), (UINT32)giMapInventoryBigItemGraphics[ iCurrentSlot ].iGraphicNum, + sX + 5, sY, MAP_INVEN_SLOT_WIDTH, MAP_INVEN_SLOT_IMAGE_HEIGHT, fOutLine, sOutLine ); + } + } + else + { + INVRenderItem( guiSAVEBUFFER, NULL, &(pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object), (INT16)(sX + 7), sY, 60, 25, DIRTYLEVEL2, NULL, 0, fOutLine, sOutLine );//67 + } SetFontDestBuffer( FRAME_BUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); @@ -402,15 +578,6 @@ BOOLEAN RenderItemInPoolSlot( INT32 iCurrentSlot, INT32 iFirstSlotOnPage ) return ( FALSE ); } - // now blit this object in the box - //BltVideoObjectOutlineFromIndex( guiSAVEBUFFER, GetInterfaceGraphicForItem( &(Item[ pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.usItem ]) ), - // Item[ pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.usItem ].ubGraphicNum, - // sCenX, sCenY, - //sOutLine, TRUE ); - - - - // now draw bar for condition // Display ststus DrawItemUIBarEx( &( pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object ), 0, @@ -439,40 +606,60 @@ BOOLEAN RenderItemInPoolSlot( INT32 iCurrentSlot, INT32 iFirstSlotOnPage ) wcscpy( sString, ShortItemNames[ pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.usItem ] ); - if( StringPixLength( sString, MAP_IVEN_FONT ) >= ( MAP_INVEN_SLOT_WIDTH ) ) + if( StringPixLength( sString, MAP_INVEN_NAME_FONT ) >= ( MAP_INVEN_SLOT_WIDTH ) ) { - ReduceStringLength( sString, ( INT16 )( MAP_INVEN_SLOT_WIDTH - StringPixLength( L" ...", MAP_IVEN_FONT ) ), MAP_IVEN_FONT ); + ReduceStringLength( sString, ( INT16 )( MAP_INVEN_SLOT_WIDTH - StringPixLength( L" ...", MAP_INVEN_NAME_FONT ) ), MAP_INVEN_NAME_FONT ); } FindFontCenterCoordinates( (INT16)( 4 + MAP_INVENTORY_POOL_SLOT_START_X + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCurrentSlot / MAP_INV_SLOT_COLS ) ) ), 0, MAP_INVEN_SLOT_WIDTH, 0, - sString, MAP_IVEN_FONT, + sString, MAP_INVEN_NAME_FONT, &sWidth, &sHeight ); SetFontDestBuffer( guiSAVEBUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); - SetFont( MAP_IVEN_FONT ); + SetFont( MAP_INVEN_NAME_FONT ); SetFontForeground( FONT_WHITE ); SetFontBackground( FONT_BLACK ); + // HEADROCK HAM 5: Offset is now changeable. mprintf( sWidth, - ( INT16 )( 3 + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y + MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCurrentSlot % ( MAP_INV_SLOT_COLS ) ) ) ) + ( INT16 )( ITEMDESC_ITEM_NAME_POOL_OFFSET_Y + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y + MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCurrentSlot % ( MAP_INV_SLOT_COLS ) ) ) ) , sString ); -/* - if( pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.ubNumberOfObjects > 1 ) + // HEADROCK HAM 5: If ALT is pressed, draw sale price. + if( _KeyDown( ALT ) ) { - swprintf( sString, L"x%d", pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object.ubNumberOfObjects ); + INT32 iPrice; + if ( _KeyDown( SHIFT ) ) + { + iPrice = SellItem( pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object, TRUE ); + } + else + { + iPrice = SellItem( pInventoryPoolList[ iCurrentSlot + iFirstSlotOnPage ].object, FALSE ); + } + + swprintf( sString, L"$%d", iPrice ); - // find font right coord - FindFontRightCoordinates( ( INT16 )( ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X - 1 + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCurrentSlot / MAP_INV_SLOT_COLS ) ) ),0, MAP_INVEN_SPACE_BTWN_SLOTS - 10, 0, sString, MAP_IVEN_FONT, &sX, &sY ); + UINT32 uiSalePriceFont = (fMapInventoryZoom ? FONT12ARIAL : FONT10ARIAL); + UINT16 usFontHeight = GetFontHeight( uiSalePriceFont ); - sY = ( INT16 )( 3 + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y + MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCurrentSlot % ( MAP_INV_SLOT_COLS ) ) ) ) - 7; + SetFontDestBuffer( guiSAVEBUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); + + SetFont( uiSalePriceFont ); + SetFontForeground( FONT_RED ); + SetFontBackground( FONT_BLACK ); + + INT16 sOffsetX = (fMapInventoryZoom ? 12 : 10); + INT16 sOffsetY = (fMapInventoryZoom ? 5 : 3); + + sX = (INT16)( sOffsetX + MAP_INVENTORY_POOL_SLOT_START_X + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCurrentSlot / MAP_INV_SLOT_COLS ) ) ); + sY = (INT16)( sOffsetY + MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCurrentSlot % MAP_INV_SLOT_COLS ) ) ); - // print string mprintf( sX, sY, sString ); + } -*/ SetFontDestBuffer( FRAME_BUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); @@ -534,6 +721,9 @@ void CreateDestroyMapInventoryPoolButtons( BOOLEAN fExitFromMapScreen ) if( ( fShowMapInventoryPool ) && ( fCreated == FALSE ) ) { + // HEADROCK HAM 5: Reset the offsets! + ResetMapInventoryOffsets(); + if( ( gWorldSectorX == sSelMapX ) && ( gWorldSectorY == sSelMapY ) && ( gbWorldSectorZ == iCurrentMapSectorZ ) ) { // handle all reachable before save @@ -557,6 +747,9 @@ void CreateDestroyMapInventoryPoolButtons( BOOLEAN fExitFromMapScreen ) // build stash BuildStashForSelectedSector( sSelMapX, sSelMapY, ( INT16 )( iCurrentMapSectorZ ) ); + // HEADROCK HAM 5: Reset all BigItem image numbers, just in case. + ResetAllMapInventoryBigItemGraphics(); + CreateMapInventoryPoolDoneButton( ); fMapPanelDirty = TRUE; @@ -586,6 +779,20 @@ void CreateDestroyMapInventoryPoolButtons( BOOLEAN fExitFromMapScreen ) DestroyInventoryPoolDoneButton( ); + // HEADROCK HAM 5: Destroy big item graphics. + if (fMapInventoryZoom) + { + UnloadAllMapInventoryBigItemGraphics(); + } + // HEADROCK HAM 5: Cancel zoom input request, if active. + if (fWaitingForZoomInput) + { + CancelInventoryZoomInput( FALSE ); // Must be false, because the button doesn't exist anymore! + } + + // Reset number of desired slots so we recalculate page size entirely on the next open. + giDesiredNumMapInventorySlots = -1; + // clear up unseen list ClearUpTempUnSeenList( ); @@ -594,7 +801,8 @@ void CreateDestroyMapInventoryPoolButtons( BOOLEAN fExitFromMapScreen ) DestroyStash( ); - + // HEADROCK HAM 5: Filters! + guiMapInventoryFilter = IC_MAPFILTER_ALL; fMapPanelDirty = TRUE; fTeamPanelDirty = TRUE; @@ -757,8 +965,7 @@ void MapInvenPoolScreenMaskCallback(MOUSE_REGION * pRegion, INT32 iReason ) return; } - -void CreateMapInventoryPoolSlots( void ) +void CreateMapInventoryPoolSlots( ) { INT32 iCounter = 0; INT16 sX = 0, sY = 0; @@ -818,20 +1025,105 @@ void MapInvenPoolSlotsMove( MOUSE_REGION * pRegion, INT32 iReason ) iCounter = MSYS_GetRegionUserData( pRegion, 0 ); - if( iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) + // HEADROCK HAM 5: While in inventory zoom input mode, we do not draw item compatibility. Instead, mousing + // over the inventory shows which items from the current page will appear on the zoomed page. This information + // is calculated below. + if (!fMapInventoryZoom && fWaitingForZoomInput) { - iCurrentlyHighLightedItem = iCounter; - fChangedInventorySlots = TRUE; - gfCheckForCursorOverMapSectorInventoryItem = TRUE; - } - else if( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) - { - iCurrentlyHighLightedItem = -1; - fChangedInventorySlots = TRUE; - gfCheckForCursorOverMapSectorInventoryItem = FALSE; + //iCounter indicates the position of our item in the current (unzoomed) pool page. - // re render radar map - RenderRadarScreen( ); + // Get the total number of slots shown on each Zoomed Page. + UINT16 usSlotsOnZoomedPage = GetInventorySlotCount( TRUE ); + + // Find our item's location in the entire sector pool. + UINT16 usItemLocationInPool = iCounter + (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + + // Which page would it be on when zoomed? + UINT16 usZoomedPage = usItemLocationInPool / usSlotsOnZoomedPage; + // Which position would it be in on that page? + UINT16 usPlaceOnZoomedPage = usItemLocationInPool % usSlotsOnZoomedPage; + + // Alright, now lets figure out what the first item on that page will be. + INT16 sFirstItemToHilite = (usZoomedPage * usSlotsOnZoomedPage); + // Where is it on the page we're looking at currently? + sFirstItemToHilite = sFirstItemToHilite - (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + // We're not interested in items that are not currently on the screen... + sFirstItemToHilite = __max(0, sFirstItemToHilite ); + + // And which is the last item that will be on the zoomed page? + INT16 sLastItemToHilite = ((usZoomedPage+1) * usSlotsOnZoomedPage) - 1; + // Where is it on the page we're looking at currently? + sLastItemToHilite = sLastItemToHilite - (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + // We're not interested in items that are not currently on the screen... + sLastItemToHilite = __min(((iCurrentInventoryPoolPage+1) * MAP_INVENTORY_POOL_SLOT_COUNT), sLastItemToHilite ); + + // Run from the first to the last item, setting them to have an outline displayed. + BOOLEAN fAnyObjectsExist = FALSE; + for (UINT16 x = 0; x < MAP_INVENTORY_POOL_SLOT_COUNT; x++) + { + if (x >= sFirstItemToHilite && x <= sLastItemToHilite) + { + gfMapInventoryItemToZoom[x] = TRUE; + } + else + { + gfMapInventoryItemToZoom[x] = FALSE; + } + } + + /* + for (UINT8 x = ubColumnOfFirstItem; x <= ubColumnOfLastItem; x++) + { + INT16 top = 0; + INT16 bottom = 0; + INT16 left = 0; + INT16 right = 0; + + if (x == ubColumnOfFirstItem) + { + top = ( INT16 )( MAP_INVENTORY_POOL_SLOT_START_Y + ( MAP_INVEN_SLOT_HEIGHT * ubPositionFirstItem ) ); + } + else + { + top = ( INT16 )( MAP_INVENTORY_POOL_SLOT_START_Y ); + } + + if (x == ubColumnOfLastItem) + { + bottom = ( INT16 )( MAP_INVENTORY_POOL_SLOT_START_Y + ( MAP_INVEN_SLOT_HEIGHT * (ubPositionLastItem+1) ) ); + } + else + { + bottom = ( INT16 )( MAP_INVENTORY_POOL_SLOT_START_Y + ( MAP_INVEN_SLOT_HEIGHT * (MAP_INV_SLOT_COLS+1) ) ); + } + + left = ( INT16 )( MAP_INVENTORY_POOL_SLOT_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X + ( MAP_INVEN_SPACE_BTWN_SLOTS * x ) ); + right = ( INT16 )( MAP_INVENTORY_POOL_SLOT_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X + ( MAP_INVEN_SPACE_BTWN_SLOTS * (x+1) ) ); + + INT16 sColor = Get16BPPColor( FROMRGB( 255, 255, 255 ) ); + DrawItemOutlineZoomedInventory( left, top, right, bottom, sColor, guiSAVEBUFFER ); + } + */ + + fMapPanelDirty = TRUE; + } + else + { + if( iReason & MSYS_CALLBACK_REASON_GAIN_MOUSE ) + { + iCurrentlyHighLightedItem = iCounter; + fChangedInventorySlots = TRUE; + gfCheckForCursorOverMapSectorInventoryItem = TRUE; + } + else if( iReason & MSYS_CALLBACK_REASON_LOST_MOUSE ) + { + iCurrentlyHighLightedItem = -1; + fChangedInventorySlots = TRUE; + gfCheckForCursorOverMapSectorInventoryItem = FALSE; + + // re render radar map + RenderRadarScreen( ); + } } } @@ -851,6 +1143,13 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) if( ( iReason & MSYS_CALLBACK_REASON_RBUTTON_UP ) ) { + // HEADROCK HAM 5: If in Zoom Input mode, cancel it + if (fWaitingForZoomInput) + { + CancelInventoryZoomInput( TRUE ); + + return; + } if(gGameExternalOptions.fSectorDesc == TRUE) { //CHRISL: Make it possible to right click and pull up stack popup and/or item description boxes @@ -858,8 +1157,20 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) bool fValidPointer = false; //CHRISL: Try to update InSector value so we don't have to "activate" a sector if(MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->sSectorX == sSelMapX && MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->sSectorY == sSelMapY && MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->bSectorZ == iCurrentMapSectorZ && !MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->flags.fBetweenSectors) + { MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->bInSector=TRUE; - if ( !InSectorStackPopup( ) && !InItemStackPopup( ) /*&& !InItemDescriptionBox( ) */ && !InKeyRingPopup( ) && twItem->object.exists() == true && (bSelectedInfoChar != -1 && gCharactersList[bSelectedInfoChar].fValid)) + } + else + { + // HEADROCK HAM 5: Unreachable items will never show an infobox. + return; + } + // HEADROCK HAM 5: Unreachable items will never show an infobox. + if (!(twItem->usFlags & WORLD_ITEM_REACHABLE)) + { + return; + } + if ( !InSectorStackPopup( ) && !InItemStackPopup( ) /*&& !InItemDescriptionBox( ) */ && !InKeyRingPopup( ) && twItem->object.exists() == true && (bSelectedInfoChar != -1 && gCharactersList[bSelectedInfoChar].fValid) ) { if(OK_CONTROL_MERC( MercPtrs[gCharactersList[bSelectedInfoChar].usSolID] )) { @@ -886,8 +1197,17 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) { DeleteItemDescriptionBox(); } - - MAPInternalInitItemDescriptionBox( &twItem->object, 0, MercPtrs[gCharactersList[bSelectedInfoChar].usSolID] ); + // HEADROCK HAM 5: Sector Inventory Item Desc Box no longer accessible during combat. + + if( !CanPlayerUseSectorInventory( &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]) ) ) + { + DoScreenIndependantMessageBox( New113HAMMessage[ 22 ], MSG_BOX_FLAG_OK, NULL ); + return; + } + else + { + MAPInternalInitItemDescriptionBox( &twItem->object, 0, MercPtrs[gCharactersList[bSelectedInfoChar].usSolID] ); + } } else if(fValidPointer) { @@ -909,6 +1229,23 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) } else if( iReason & MSYS_CALLBACK_REASON_LBUTTON_UP ) { + // HEADROCK HAM 5: If in Zoom Input mode, enter zoom mode using this item's location to determine + // which page we want to view. + if (fWaitingForZoomInput) + { + // Figure out which page we need to display. + UINT16 usSlotsOnZoomedPage = GetInventorySlotCount( TRUE ); + UINT16 usItemLocationInPool = iCounter + (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + UINT16 usTargetPage = usItemLocationInPool / usSlotsOnZoomedPage; + + giDesiredNumMapInventorySlots = (__max(usTargetPage * usSlotsOnZoomedPage, (UINT16)(pInventoryPoolList.size() / usSlotsOnZoomedPage) + usSlotsOnZoomedPage)); + + CancelInventoryZoomInput( FALSE ); + HandleMapInventoryZoom( usTargetPage, iCounter ); + + return; + } + // check if item in cursor, if so, then swap, and no item in curor, pick up, if item in cursor but not box, put in box if ( gpItemPointer == NULL ) @@ -1038,10 +1375,11 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) // Else, try to place here if ( PlaceObjectInInventoryStash( &( pInventoryPoolList[ ( iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT ) + iCounter ].object ), gpItemPointer ) ) { + // HEADROCK HAM 5: A LOT of functions rely on these flags being set. So set them!! + pInventoryPoolList[(iCurrentInventoryPoolPage*MAP_INVENTORY_POOL_SLOT_COUNT)+iCounter].bVisible = TRUE; + pInventoryPoolList[(iCurrentInventoryPoolPage*MAP_INVENTORY_POOL_SLOT_COUNT)+iCounter].fExists = TRUE; if(gGameExternalOptions.fEnableInventoryPoolQ)//dnl ch51 091009 { - pInventoryPoolList[(iCurrentInventoryPoolPage*MAP_INVENTORY_POOL_SLOT_COUNT)+iCounter].bVisible = TRUE; - pInventoryPoolList[(iCurrentInventoryPoolPage*MAP_INVENTORY_POOL_SLOT_COUNT)+iCounter].fExists = TRUE; if(!GridNoOnVisibleWorldTile(sObjectSourceGridNo)) sObjectSourceGridNo = gMapInformation.sCenterGridNo; } @@ -1101,43 +1439,246 @@ void MapInvenPoolSlots(MOUSE_REGION * pRegion, INT32 iReason ) // dirty region, force update fMapPanelDirty = TRUE; - } } void CreateMapInventoryButtons( void ) { - guiMapInvenButtonImage[ 0 ]= LoadButtonImage( "INTERFACE\\map_screen_bottom_arrows.sti" , 10, 1, -1, 3, -1 ); - guiMapInvenButton[ 0 ] = QuickCreateButton( guiMapInvenButtonImage[ 0 ], (MAP_INV_X_OFFSET + 559), (SCREEN_HEIGHT - 144 - 2 * yResOffset), + guiMapInvenArrowButtonImage[ 0 ]= LoadButtonImage( "INTERFACE\\map_screen_bottom_arrows.sti" , 10, 1, -1, 3, -1 ); + guiMapInvenArrowButton[ 0 ] = QuickCreateButton( guiMapInvenArrowButtonImage[ 0 ], (MAP_INV_X_OFFSET + 559), (SCREEN_HEIGHT - 144 - 2 * yResOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)MapInventoryPoolNextBtn ); - guiMapInvenButtonImage[ 1 ]= LoadButtonImage( "INTERFACE\\map_screen_bottom_arrows.sti" ,9, 0, -1, 2, -1 ); - guiMapInvenButton[ 1 ] = QuickCreateButton( guiMapInvenButtonImage[ 1 ], (MAP_INV_X_OFFSET + 487), (SCREEN_HEIGHT - 144 - 2 * yResOffset), + guiMapInvenArrowButtonImage[ 1 ]= LoadButtonImage( "INTERFACE\\map_screen_bottom_arrows.sti" ,9, 0, -1, 2, -1 ); + guiMapInvenArrowButton[ 1 ] = QuickCreateButton( guiMapInvenArrowButtonImage[ 1 ], (MAP_INV_X_OFFSET + 487), (SCREEN_HEIGHT - 144 - 2 * yResOffset), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)MapInventoryPoolPrevBtn ); // set up fast help text - SetButtonFastHelpText( guiMapInvenButton[ 0 ], pMapScreenInvenButtonHelpText[ 0 ] ); - SetButtonFastHelpText( guiMapInvenButton[ 1 ], pMapScreenInvenButtonHelpText[ 1 ] ); + SetButtonFastHelpText( guiMapInvenArrowButton[ 0 ], pMapScreenInvenButtonHelpText[ 0 ] ); + SetButtonFastHelpText( guiMapInvenArrowButton[ 1 ], pMapScreenInvenButtonHelpText[ 1 ] ); + + // HEADROCK HAM 5: Zoom Button + // Currently available only at 800x600 resolution or higher. There is simply not enough space for more than 6 + // bigitem slots on 640x480. + if (iResolution >= _800x600 ) + { + guiMapInvenZoomButtonImage = LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 0, 0, -1, 1, -1 ); + guiMapInvenZoomButton = QuickCreateButton( guiMapInvenZoomButtonImage, INVEN_POOL_X+10, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolZoomBtn ); + + SetButtonFastHelpText( guiMapInvenZoomButton, pMapScreenInvenButtonHelpText[ 3 ] ); + // Set to current selected state. + if (fMapInventoryZoom) + { + ButtonList[ guiMapInvenZoomButton ]->uiFlags |= (BUTTON_CLICKED_ON); + } + } + + if (iResolution >= _800x600 ) + { + // HEADROCK HAM 5: Stack and Merge button + guiMapInvenSortButtonImage[ 0 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 16, 14, -1, 15, -1 ); + guiMapInvenSortButton[ 0 ] = QuickCreateButton( guiMapInvenSortButtonImage[ 0 ], INVEN_POOL_X+55, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolStackAndMergeBtn ); + + SetButtonFastHelpText( guiMapInvenSortButton[ 0 ], pMapScreenInvenButtonHelpText[ 4 ] ); + + // HEADROCK HAM 5: Sort Ammo Button + guiMapInvenSortButtonImage[ 1 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 4, 2, -1, 3, -1 ); + guiMapInvenSortButton[ 1 ] = QuickCreateButton( guiMapInvenSortButtonImage[ 1 ], INVEN_POOL_X+85, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolSortAmmoBtn ); + + SetButtonFastHelpText( guiMapInvenSortButton[ 1 ], pMapScreenInvenButtonHelpText[ 5 ] ); + + // HEADROCK HAM 5: Sort Attachments Button + guiMapInvenSortButtonImage[ 2 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 10, 8, -1, 9, -1 ); + guiMapInvenSortButton[ 2 ] = QuickCreateButton( guiMapInvenSortButtonImage[ 2 ], INVEN_POOL_X+115, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolSortAttachmentsBtn ); + + SetButtonFastHelpText( guiMapInvenSortButton[ 2 ], pMapScreenInvenButtonHelpText[ 6 ] ); + + // HEADROCK HAM 5: Sort Attachments Button + guiMapInvenSortButtonImage[ 3 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 13, 11, -1, 12, -1 ); + guiMapInvenSortButton[ 3 ] = QuickCreateButton( guiMapInvenSortButtonImage[ 3 ], INVEN_POOL_X+145, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolEjectAmmoBtn ); + + SetButtonFastHelpText( guiMapInvenSortButton[ 3 ], pMapScreenInvenButtonHelpText[ 7 ] ); + } + + // HEADROCK HAM 5: Filter Toggle Button + if (iResolution >= _800x600 ) + { + guiMapInvenFilterButtonImage[ 0 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 7, 5, -1, 6, -1 ); + guiMapInvenFilterButton[ 0 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 0 ], INVEN_POOL_X+190, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 0 ], pMapScreenInvenButtonHelpText[ 8 ] ); + ButtonList[ guiMapInvenFilterButton[ 0 ] ]->UserData[0] = 0; + ButtonList[ guiMapInvenFilterButton[ 0 ] ]->UserData[1] = IC_MAPFILTER_ALL; + ButtonList[ guiMapInvenFilterButton[ 0 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 0 ] ]->UserData[3] = 0; + + guiMapInvenFilterButtonImage[ 1 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 20, 20, -1, 21, -1 ); + guiMapInvenFilterButton[ 1 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 1 ], INVEN_POOL_X+225, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 1 ], pMapScreenInvenButtonHelpText[ 9 ] ); + ButtonList[ guiMapInvenFilterButton[ 1 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 1 ] ]->UserData[1] = IC_MAPFILTER_GUN; + ButtonList[ guiMapInvenFilterButton[ 1 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 1 ] ]->UserData[3] = IC_MAPFILTER_GUN; + + guiMapInvenFilterButtonImage[ 2 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 22, 22, -1, 23, -1 ); + guiMapInvenFilterButton[ 2 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 2 ], INVEN_POOL_X+225, INVEN_POOL_Y+24, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 2 ], pMapScreenInvenButtonHelpText[ 10 ] ); + ButtonList[ guiMapInvenFilterButton[ 2 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 2 ] ]->UserData[1] = IC_MAPFILTER_AMMO; + ButtonList[ guiMapInvenFilterButton[ 2 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 2 ] ]->UserData[3] = IC_MAPFILTER_AMMO; + + guiMapInvenFilterButtonImage[ 3 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 24, 24, -1, 25, -1 ); + guiMapInvenFilterButton[ 3 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 3 ], INVEN_POOL_X+255, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 3 ], pMapScreenInvenButtonHelpText[ 11 ] ); + ButtonList[ guiMapInvenFilterButton[ 3 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 3 ] ]->UserData[1] = IC_MAPFILTER_EXPLOSV; + ButtonList[ guiMapInvenFilterButton[ 3 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 3 ] ]->UserData[3] = IC_MAPFILTER_EXPLOSV; + + guiMapInvenFilterButtonImage[ 4 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 26, 26, -1, 27, -1 ); + guiMapInvenFilterButton[ 4 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 4 ], INVEN_POOL_X+255, INVEN_POOL_Y+24, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 4 ], pMapScreenInvenButtonHelpText[ 12 ] ); + ButtonList[ guiMapInvenFilterButton[ 4 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 4 ] ]->UserData[1] = IC_MAPFILTER_MELEE; + ButtonList[ guiMapInvenFilterButton[ 4 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 4 ] ]->UserData[3] = IC_MAPFILTER_MELEE; + + guiMapInvenFilterButtonImage[ 5 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 28, 28, -1, 29, -1 ); + guiMapInvenFilterButton[ 5 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 5 ], INVEN_POOL_X+285, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 5 ], pMapScreenInvenButtonHelpText[ 13 ] ); + ButtonList[ guiMapInvenFilterButton[ 5 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 5 ] ]->UserData[1] = IC_MAPFILTER_ARMOR; + ButtonList[ guiMapInvenFilterButton[ 5 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 5 ] ]->UserData[3] = IC_MAPFILTER_ARMOR; + + guiMapInvenFilterButtonImage[ 6 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 30, 30, -1, 31, -1 ); + guiMapInvenFilterButton[ 6 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 6 ], INVEN_POOL_X+285, INVEN_POOL_Y+24, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 6 ], pMapScreenInvenButtonHelpText[ 14 ] ); + ButtonList[ guiMapInvenFilterButton[ 6 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 6 ] ]->UserData[1] = IC_MAPFILTER_LBE; + ButtonList[ guiMapInvenFilterButton[ 6 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 6 ] ]->UserData[3] = IC_MAPFILTER_LBE; + + guiMapInvenFilterButtonImage[ 7 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 32, 32, -1, 33, -1 ); + guiMapInvenFilterButton[ 7 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 7 ], INVEN_POOL_X+315, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 7 ], pMapScreenInvenButtonHelpText[ 15 ] ); + ButtonList[ guiMapInvenFilterButton[ 7 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 7 ] ]->UserData[1] = IC_MAPFILTER_KIT; + ButtonList[ guiMapInvenFilterButton[ 7 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 7 ] ]->UserData[3] = IC_MAPFILTER_KIT; + + guiMapInvenFilterButtonImage[ 8 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 34, 34, -1, 35, -1 ); + guiMapInvenFilterButton[ 8 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 8 ], INVEN_POOL_X+315, INVEN_POOL_Y+24, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 8 ], pMapScreenInvenButtonHelpText[ 16 ] ); + ButtonList[ guiMapInvenFilterButton[ 8 ] ]->UserData[0] = 1; + ButtonList[ guiMapInvenFilterButton[ 8 ] ]->UserData[1] = IC_MAPFILTER_MISC; + ButtonList[ guiMapInvenFilterButton[ 8 ] ]->UserData[2] = 0; + ButtonList[ guiMapInvenFilterButton[ 8 ] ]->UserData[3] = IC_MAPFILTER_MISC; + + /* + guiMapInvenFilterButtonImage[ 9 ]= LoadButtonImage( "INTERFACE\\sector_inventory_buttons.sti" , 19, 17, -1, 18, -1 ); + guiMapInvenFilterButton[ 9 ] = QuickCreateButton( guiMapInvenFilterButtonImage[ 9 ], INVEN_POOL_X+349, INVEN_POOL_Y+10, + BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, + NULL, (GUI_CALLBACK)MapInventoryPoolFilterBtn ); + + SetButtonFastHelpText( guiMapInvenFilterButton[ 9 ], pMapScreenInvenButtonHelpText[ 17 ] ); + ButtonList[ guiMapInvenFilterButton[ 9 ] ]->UserData[0] = 0; + ButtonList[ guiMapInvenFilterButton[ 9 ] ]->UserData[1] = 0; + */ + } //reset the current inventory page to be the first page iCurrentInventoryPoolPage = 0; + HandleSetFilterButtons(); + return; } void DestroyMapInventoryButtons( void ) { + // Page Arrow Buttons + RemoveButton( guiMapInvenArrowButton[ 0 ] ); + RemoveButton( guiMapInvenArrowButton[ 1 ] ); + UnloadButtonImage( guiMapInvenArrowButtonImage[ 0 ] ); + UnloadButtonImage( guiMapInvenArrowButtonImage[ 1 ] ); + // HEADROCK HAM 5: Destroy Inventory Zoom Button + if (iResolution >= _800x600 ) + { + RemoveButton( guiMapInvenZoomButton ); + UnloadButtonImage( guiMapInvenZoomButtonImage ); + } - RemoveButton( guiMapInvenButton[ 0 ] ); - RemoveButton( guiMapInvenButton[ 1 ] ); + // HEADROCK HAM 5: Auto-Sort buttons + if (iResolution >= _800x600 ) + { + RemoveButton( guiMapInvenSortButton[ 0 ] ); + UnloadButtonImage( guiMapInvenSortButtonImage[ 0 ] ); + RemoveButton( guiMapInvenSortButton[ 1 ] ); + UnloadButtonImage( guiMapInvenSortButtonImage[ 1 ] ); + RemoveButton( guiMapInvenSortButton[ 2 ] ); + UnloadButtonImage( guiMapInvenSortButtonImage[ 2 ] ); + RemoveButton( guiMapInvenSortButton[ 3 ] ); + UnloadButtonImage( guiMapInvenSortButtonImage[ 3 ] ); + } + // HEADROCK HAM 5: Filter button + if (iResolution >= _800x600 ) + { + for (INT32 iCounter = 0; iCounter < 9; iCounter++) + { + RemoveButton( guiMapInvenFilterButton[ iCounter ] ); + UnloadButtonImage( guiMapInvenFilterButtonImage[ iCounter ] ); + } - UnloadButtonImage( guiMapInvenButtonImage[ 0 ] ); - UnloadButtonImage( guiMapInvenButtonImage[ 1 ] ); + /* + // Destroy the popup. + delete(gMapInventoryFilterPopup); + gMapInventoryFilterPopup = NULL; + gfMapInventoryFilterPopupInitialized = FALSE; + gfMapInventoryFilterPopupVisible = FALSE; + */ + } return; } @@ -1581,7 +2122,8 @@ void BeginInventoryPoolPtr( OBJECTTYPE *pInventorySlot ) } else if ( _KeyDown ( ALT ) && fSELLALL)//Sell Item { - INT32 iPrice = SellItem( gItemPointer ); + // HEADROCK HAM 5: Added argument + INT32 iPrice = SellItem( gItemPointer, TRUE ); PlayJA2Sample( COMPUTER_BEEP2_IN, RATE_11025, 15, 1, MIDDLEPAN ); gpItemPointer = NULL; fMapInventoryItem = FALSE; @@ -1593,16 +2135,18 @@ void BeginInventoryPoolPtr( OBJECTTYPE *pInventorySlot ) //if ( pInventoryPoolList[ iNumber ].object.usItem == gItemPointer.usItem ) if ( pInventoryPoolList[ iNumber ].object.usItem == gItemPointer.usItem && pInventoryPoolList[ iNumber ].usFlags & WORLD_ITEM_REACHABLE) { - iPrice += SellItem( pInventoryPoolList[ iNumber ].object ); + // HEADROCK HAM 5: Added argument + iPrice += SellItem( pInventoryPoolList[ iNumber ].object, TRUE ); DeleteObj( &pInventoryPoolList [ iNumber ].object ); } } } //ADB you can sell items for 0, but that's not fair //and it's not easy to stop the sale so make the price 1 - if (iPrice == 0) { - iPrice = 1; - } + // HEADROCK HAM 5: Fair Schmair. They can now see that the sale value is $0 anyway, it's up to them. + //if (iPrice == 0) { + // iPrice = 1; + //} if ( _KeyDown ( 89 )) //Lalien: sell all items of this type on Alt+Y { @@ -1612,7 +2156,11 @@ void BeginInventoryPoolPtr( OBJECTTYPE *pInventorySlot ) ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_SOLD] ); } - AddTransactionToPlayersBook( SOLD_ITEMS, 0, GetWorldTotalMin(), iPrice ); + // HEADROCK HAM 5: No transaction if no money. + if (iPrice > 0) + { + AddTransactionToPlayersBook( SOLD_ITEMS, 0, GetWorldTotalMin(), iPrice ); + } if ( fShowMapInventoryPool ) HandleButtonStatesWhileMapInventoryActive(); @@ -1791,7 +2339,212 @@ void MapInventoryPoolDoneBtn( GUI_BUTTON *btn, INT32 reason ) } } +// HEADROCK HAM 5: Sort ammo button callback +void MapInventoryPoolSortAmmoBtn( GUI_BUTTON *btn, INT32 reason ) +{ + if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN || reason & MSYS_CALLBACK_REASON_RBUTTON_DWN) + { + if (!(btn->uiFlags & (BUTTON_CLICKED_ON))) + { + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + SortSectorInventoryAmmo(false); + btn->uiFlags &=~ (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_RBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + SortSectorInventoryAmmo(true); + btn->uiFlags &=~ (BUTTON_CLICKED_ON); + } + } +} +// HEADROCK HAM 5: Stack and merge button callback +void MapInventoryPoolStackAndMergeBtn( GUI_BUTTON *btn, INT32 reason ) +{ + if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN) + { + if (!(btn->uiFlags & (BUTTON_CLICKED_ON))) + { + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + SortSectorInventoryStackAndMerge(false); + btn->uiFlags &=~ (BUTTON_CLICKED_ON); + } + } +} + +// HEADROCK HAM 5: Sort attachments button callback +void MapInventoryPoolSortAttachmentsBtn( GUI_BUTTON *btn, INT32 reason ) +{ + if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN) + { + if (!(btn->uiFlags & (BUTTON_CLICKED_ON))) + { + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + SortSectorInventorySeparateAttachments(); + btn->uiFlags &=~ (BUTTON_CLICKED_ON); + } + } +} + +// HEADROCK HAM 5: Eject Ammo button callback +void MapInventoryPoolEjectAmmoBtn( GUI_BUTTON *btn, INT32 reason ) +{ + if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN) + { + if (!(btn->uiFlags & (BUTTON_CLICKED_ON))) + { + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + SortSectorInventoryEjectAmmo(); + btn->uiFlags &=~ (BUTTON_CLICKED_ON); + } + } +} + +// HEADROCK HAM 5: Preliminary Filter Button +void MapInventoryPoolFilterBtn( GUI_BUTTON *btn, INT32 reason ) +{ + if (reason & MSYS_CALLBACK_REASON_LBUTTON_DWN || + reason & MSYS_CALLBACK_REASON_RBUTTON_DWN ) + { + if (!(btn->uiFlags & (BUTTON_CLICKED_ON))) + { + // Set as "clicked on", but do nothing until the mouse is released. + btn->uiFlags |= (BUTTON_CLICKED_ON); + } + } + if (reason & MSYS_CALLBACK_REASON_LBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + if (btn->UserData[0] == 0) + { + MapInventoryFilterSet( btn->UserData[1] ); + } + else + { + MapInventoryFilterToggle( btn->UserData[1] ); + } + + // HEADROCK HAM 5: Disabled for now, as we've got buttons for this. + //CreateMapInventoryFilterMenu( ); + } + } + if (reason & MSYS_CALLBACK_REASON_RBUTTON_UP) + { + if (btn->uiFlags & (BUTTON_CLICKED_ON)) + { + if (btn->UserData[2] == 0) + { + MapInventoryFilterSet( btn->UserData[3] ); + } + else + { + MapInventoryFilterToggle( btn->UserData[3] ); + } + + // HEADROCK HAM 5: Disabled for now, as we've got buttons for this. + //CreateMapInventoryFilterMenu( ); + } + } +} + +// HEADROCK HAM 5: Zoom button. +// This button handles toggling between the small-scale inventory (items shown at normal size, similar to merc's inventory) +// and the big-item inventory (which shows BigItem pics and is therefore much easier to examine in detail). +// A click on the button initiates "zoom input" mode, allowing the player to decide which part of the small-scale +// inventory he would like to examine more closely. While in this mode, you may flip inventory pages or close the inventory +// altogether, but may not move the mouse outside the map inventory region. Right-click will cancel this mode +// automatically. +void MapInventoryPoolZoomBtn( GUI_BUTTON *btn, INT32 reason ) +{ + // Only left-click interacts with this button. + if(reason & MSYS_CALLBACK_REASON_LBUTTON_UP ) + { + // Is the button already pressed? + if (btn->uiFlags & BUTTON_CLICKED_ON) + { + // Are we still waiting for zoom input? If so, the button is depressed, but we are still not + // in zoomed mode. + if (fWaitingForZoomInput) + { + // Turn off Zoom Input mode. + CancelInventoryZoomInput(TRUE); + } + // If zoomed, unzoom. + if (fMapInventoryZoom) + { + // What is the number of the first item on the current inventory page? + UINT16 usFirstItem = iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT; + UINT16 usNumSlotsOnUnzoomedPage = GetInventorySlotCount( FALSE ); + UINT16 usNewPage = (UINT16)(usFirstItem / usNumSlotsOnUnzoomedPage); + HandleMapInventoryUnzoom( usNewPage ); + } + + // Turn the button off so it can be clicked again. + btn->uiFlags &=~(BUTTON_CLICKED_ON); + } + else + { + // This will initiate Zoom Input mode. + if (!fWaitingForZoomInput) + { + // Turn the button on do indicate that it has been pressed. + btn->uiFlags |= (BUTTON_CLICKED_ON); + + // Calculate the number of inventory rows and columns we have on-screen currently. + INT16 sRows = ( MAP_INVENTORY_POOL_SLOT_COUNT / MAP_INV_SLOT_COLS ); + INT16 sCols = ( MAP_INV_SLOT_COLS ); + + // Calculate the size of the inventory portion. Unfortunately there are still no constants + // for this. + MapInventoryRect.iLeft = MAP_INVENTORY_POOL_SLOT_START_X+4; + MapInventoryRect.iRight = MAP_INVENTORY_POOL_SLOT_START_X + (sRows * MAP_INVEN_SPACE_BTWN_SLOTS); + MapInventoryRect.iTop = 5; + MapInventoryRect.iBottom = MAP_INVENTORY_POOL_SLOT_START_Y + (sCols * MAP_INVEN_SLOT_HEIGHT) + 54; + // Restrict mouse movement to this area only. This will avoid interacting with unexpected portions of + // the screen, allowing us to better control confirming or exiting Zoom Input mode. + RestrictMouseCursor( &MapInventoryRect ); + + // Notify to the rest of the program that we are inside zoom input mode. In particular, inventory + // slots will behave differently when clicked or right-clicked while this flag is set. + fWaitingForZoomInput = 1; + } + else + { + // Zoom Input mode was active, which isn't supposed to happen because the button was off. Still, make + // sure the zoom input is canceled so we can go on without errors. + CancelInventoryZoomInput( TRUE ); + } + } + } +} void DisplayPagesForMapInventoryPool( void ) { @@ -1885,14 +2638,13 @@ void DrawNumberOfIventoryPoolItems( void ) void CreateMapInventoryPoolDoneButton( void ) { // create done button - guiMapInvenButtonImage[ 2 ]= LoadButtonImage( "INTERFACE\\done_button.sti" , -1, 0, -1, 1, -1 ); - - guiMapInvenButton[ 2 ] = QuickCreateButton( guiMapInvenButtonImage[ 2 ], MAP_INV_X_OFFSET + 587 , (yResSize - 147), + guiMapInvenDoneButtonImage= LoadButtonImage( "INTERFACE\\done_button.sti" , -1, 0, -1, 1, -1 ); + guiMapInvenDoneButton = QuickCreateButton( guiMapInvenDoneButtonImage, MAP_INV_X_OFFSET + 587 , (yResSize - 147), BUTTON_TOGGLE, MSYS_PRIORITY_HIGHEST, (GUI_CALLBACK)BtnGenericMouseMoveButtonCallback, (GUI_CALLBACK)MapInventoryPoolDoneBtn ); // set up fast help text - SetButtonFastHelpText( guiMapInvenButton[ 2 ], pMapScreenInvenButtonHelpText[ 2 ] ); + SetButtonFastHelpText( guiMapInvenDoneButton, pMapScreenInvenButtonHelpText[ 2 ] ); return; } @@ -1901,8 +2653,8 @@ void DestroyInventoryPoolDoneButton( void ) { // destroy ddone button - RemoveButton( guiMapInvenButton[ 2 ] ); - UnloadButtonImage( guiMapInvenButtonImage[ 2 ] ); + RemoveButton( guiMapInvenDoneButton ); + UnloadButtonImage( guiMapInvenDoneButtonImage ); return; @@ -1933,41 +2685,124 @@ void DisplayCurrentSector( void ) SetFontDestBuffer( FRAME_BUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); } - +// HEADROCK HAM 5: Following some issues with the resizing of the map inventory - especially due to switching +// between zoomed and unzoomed move, but possibly some pre-existing issues as well. Also, this function is now +// significantly more readable. void ResizeInventoryList( void ) { - if (pInventoryPoolList.empty() == true) { - pInventoryPoolList.resize(MAP_INVENTORY_POOL_SLOT_COUNT); + // If we've got no items in this sector... + if (pInventoryPoolList.empty() == true ) + { + // Resize the list to at least one page in size, containing no items. + giDesiredNumMapInventorySlots = __max(giDesiredNumMapInventorySlots, MAP_INVENTORY_POOL_SLOT_COUNT); + pInventoryPoolList.resize(giDesiredNumMapInventorySlots); + iLastInventoryPoolPage = (giDesiredNumMapInventorySlots / MAP_INVENTORY_POOL_SLOT_COUNT) - 1; + + // Done! + return; } - int emptySlots = 0; - //if it's not the exact size of a page, then make it so - if (pInventoryPoolList.size() % MAP_INVENTORY_POOL_SLOT_COUNT) { - emptySlots = MAP_INVENTORY_POOL_SLOT_COUNT - (pInventoryPoolList.size() % MAP_INVENTORY_POOL_SLOT_COUNT); - pInventoryPoolList.resize(pInventoryPoolList.size() + emptySlots); - } + // NEVER create an inventory that's SMALLER than the one we're looking at. + giDesiredNumMapInventorySlots = __max(giDesiredNumMapInventorySlots, ((iCurrentInventoryPoolPage+1) * MAP_INVENTORY_POOL_SLOT_COUNT)); - //This lets us determine whether to create a new page based on the contents of the last page instead of every page - // which lets players move things around without having to first hunt down and fill up blank slots on other pages - iLastInventoryPoolPage = ( ( pInventoryPoolList.size() - 1 ) / MAP_INVENTORY_POOL_SLOT_COUNT ); - int lastPageIndex = iLastInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT; + // Lets see how many item slots we're going to need int activeSlotsOnPage = 0; - for (int x = 0; x < MAP_INVENTORY_POOL_SLOT_COUNT; ++x) { + int activeSlotsTotal = 0; + INT32 x = 1; + INT32 ListSize = pInventoryPoolList.size(); // Current size, so we can run through it all. + UINT32 iNumEmptySlots = 0; + UINT32 iNumEmptySlotsAtEnd = 0; + // Run through the entire sector inventory, collecting data. + for (x = 0; x < ListSize; x++) + { //don't test fExists because that hasn't been set yet, test object.exists - if (pInventoryPoolList[lastPageIndex + x].object.exists() == true) { - ++activeSlotsOnPage; + if (pInventoryPoolList[x].object.exists() == true) + { + // Item exists. Increase the count of valid items. + activeSlotsTotal++; + // Since the last item we (just now) checked exists, lets reset the count of how many + // slots are empty at the end of the inventory array. + iNumEmptySlotsAtEnd = 0; + + // Now count the number of items found since the end of the "previous" page. + // We constantly assume that we're looking at the end of the last page. If we find more + // items beyond this, that means there's yet another page to check, and so on until + // we actually reach the end of the array. + if (x % MAP_INVENTORY_POOL_SLOT_COUNT == 0 ) + { + // Reset the counter, this is the first item on a new page. + activeSlotsOnPage = 1; + } + else + { + // Increase the counter, building up towards the end of the page. + activeSlotsOnPage++; + } + + } + else + { + // Found an empty slot. + // Increase the number of empty slots we've encountered... + iNumEmptySlots++; + // Increase the number of slots encountered since the last valid item. + iNumEmptySlotsAtEnd++; } } - //if there aren't enough empty slots we need to grow it further - emptySlots = MAP_INVENTORY_POOL_SLOT_COUNT - activeSlotsOnPage; - if (emptySlots < 3) { - //we want 1 blank page - pInventoryPoolList.resize(pInventoryPoolList.size() + MAP_INVENTORY_POOL_SLOT_COUNT); + // Truncate. This makes sure we don't have a page full of empty slots. If we need one, we'll add it later. + // 1. Find out how many redundant empty slots we have at the end. + INT32 iNumSlotsToRemove = iNumEmptySlotsAtEnd - (iNumEmptySlotsAtEnd % MAP_INVENTORY_POOL_SLOT_COUNT); + // 2. Reduce the number of required slots at the end to make the last page. + iNumEmptySlotsAtEnd -= iNumSlotsToRemove; + // 3. Reduce the total number of empty slots encountered by the same amount. + iNumEmptySlots -= iNumSlotsToRemove; + // 4. The difference between these two values is the number of empty slots strewn between valid items. + // We need to count them separately because for all intents and purposes, we count them as valid items. + INT32 iNumEmptySlotsWithin = iNumEmptySlots - iNumEmptySlotsAtEnd; + + // Calculate the minimum size of the inventory, including both valid items and empty slots between them, + // but not the extra slots required to make a round number of pages. + ListSize = activeSlotsTotal + iNumEmptySlotsWithin; + + // Now find out the minimal number of slots required to make a round number of pages. + INT32 iOptimalSizeWithExtraEmptySlots = ((ListSize / MAP_INVENTORY_POOL_SLOT_COUNT) + 1) * MAP_INVENTORY_POOL_SLOT_COUNT; + + // This simply ensures we don't make more extra empty slots at the end than is absolutely necessary. + iNumEmptySlotsAtEnd = __min(MAP_INVENTORY_POOL_SLOT_COUNT - 1, iOptimalSizeWithExtraEmptySlots - ListSize); + + //////////////////////////////////////////////////////////////////////////// + // Now we resize the inventory to its full intended size. + // + // Note that below we use __max to make sure that we're not decreasing the number of pages while the inventory + // screen is open. In other words, the number of pages will not shrink unless we explicitly say it can. This + // can lead to having 0 items on 14 pages, for example if we delete all items from a huge sector inventory. However, + // this is fine because the extra pages will disappear when we reopen this inventory - and until then we avoid + // any issues that may arise from checking invalid inventory slots. A more experienced programmer might be able + // to reduce the size of the inventory while we work, but that would require more wizardry - and is not actually + // necessary to begin with. It's actually better and safer this way! + BOOLEAN fExtraPage = FALSE; + if (iNumEmptySlotsAtEnd >= 3 ) + { + // Lots of empty slots at the end, so we don't need to increase the number of pages. + pInventoryPoolList.resize( __max(iOptimalSizeWithExtraEmptySlots, giDesiredNumMapInventorySlots) ); + } + else + { + // We want 1 extra blank page at the end, so we add the number of slots required to make that page. + pInventoryPoolList.resize( __max(iOptimalSizeWithExtraEmptySlots + MAP_INVENTORY_POOL_SLOT_COUNT, giDesiredNumMapInventorySlots ) ); + fExtraPage = TRUE; } - //do this again, it may have changed - iLastInventoryPoolPage = ( ( pInventoryPoolList.size() - 1 ) / MAP_INVENTORY_POOL_SLOT_COUNT ); + // Set this value so that the inventory does not shrink later. If we zoom or close the inventory, this will be + // automatically reset to force a complete recalculation - which is ok because we're recalculating everything in + // such a case anyway. + giDesiredNumMapInventorySlots = pInventoryPoolList.size(); + + // Calculate the number of pages we've got. + //iLastInventoryPoolPage = ( ( pInventoryPoolList.size() - 1 ) / MAP_INVENTORY_POOL_SLOT_COUNT ); + iLastInventoryPoolPage = ( ( iOptimalSizeWithExtraEmptySlots - 1 ) / MAP_INVENTORY_POOL_SLOT_COUNT ) + fExtraPage; + iCurrentInventoryPoolPage = __min(iCurrentInventoryPoolPage, iLastInventoryPoolPage); return; } @@ -2008,34 +2843,55 @@ void HandleButtonStatesWhileMapInventoryActive( void ) return; } - // first page, can't go back any - if( iCurrentInventoryPoolPage == 0 ) - { - DisableButton( guiMapInvenButton[ 1 ] ); - } - else - { - EnableButton( guiMapInvenButton[ 1 ] ); - } - // last page, go no further if( iCurrentInventoryPoolPage == iLastInventoryPoolPage ) { - DisableButton( guiMapInvenButton[ 0 ] ); + DisableButton( guiMapInvenArrowButton[ 0 ] ); } else { - EnableButton( guiMapInvenButton[ 0 ] ); + EnableButton( guiMapInvenArrowButton[ 0 ] ); + } + + // first page, can't go back any + if( iCurrentInventoryPoolPage == 0 ) + { + DisableButton( guiMapInvenArrowButton[ 1 ] ); + } + else + { + EnableButton( guiMapInvenArrowButton[ 1 ] ); } // item picked up ..disable button if( gMPanelRegion.Cursor == EXTERN_CURSOR ) { - DisableButton( guiMapInvenButton[ 2 ] ); + DisableButton( guiMapInvenDoneButton ); + DisableButton( guiMapInvenSortButton[ 0 ] ); + DisableButton( guiMapInvenSortButton[ 1 ] ); + DisableButton( guiMapInvenSortButton[ 2 ] ); + DisableButton( guiMapInvenSortButton[ 3 ] ); } else { - EnableButton( guiMapInvenButton[ 2 ] ); + EnableButton( guiMapInvenDoneButton ); + EnableButton( guiMapInvenSortButton[ 0 ] ); + EnableButton( guiMapInvenSortButton[ 1 ] ); + EnableButton( guiMapInvenSortButton[ 2 ] ); + EnableButton( guiMapInvenSortButton[ 3 ] ); + } + + // Selected Merc is in sector? Or is in combat? + if(MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->sSectorX != sSelMapX || + MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->sSectorY != sSelMapY || + MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->bSectorZ != iCurrentMapSectorZ || + MercPtrs[gCharactersList[bSelectedInfoChar].usSolID]->flags.fBetweenSectors || + !CanPlayerUseSectorInventory( &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]) ) ) + { + DisableButton( guiMapInvenSortButton[ 0 ] ); + DisableButton( guiMapInvenSortButton[ 1 ] ); + DisableButton( guiMapInvenSortButton[ 2 ] ); + DisableButton( guiMapInvenSortButton[ 3 ] ); } } @@ -2052,7 +2908,8 @@ void DrawTextOnSectorInventory( void ) swprintf(sString, L"Inventory Pool %c", gInventoryPoolIndex); SetFontDestBuffer( guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); - FindFontCenterCoordinates( (SCREEN_WIDTH - INTERFACE_WIDTH)/2 + 271, 18, xResSize - 271, GetFontHeight( FONT14ARIAL ), sString, FONT14ARIAL, &sX, &sY ); + // HEADROCK HAM 5: Align right to make room for buttons. + FindFontRightCoordinates( (SCREEN_WIDTH - INTERFACE_WIDTH)/2 + 271, 18, xResSize - 271, GetFontHeight( FONT14ARIAL ), sString, FONT14ARIAL, &sX, &sY ); SetFont( FONT14ARIAL ); SetFontForeground( FONT_WHITE ); @@ -2140,6 +2997,12 @@ void HandleMouseInCompatableItemForMapSectorInventory( INT32 iCurrentSlot ) return; } + // Check also that we're not beyond the resize. + if (pInventoryPoolList.size() < (UINT32)(iCurrentSlot + ( iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT ) )) + { + return; + } + // given this slot value, check if anything in the displayed sector inventory or on the mercs inventory is compatable if( fShowInventoryFlag ) { @@ -2215,7 +3078,11 @@ void ResetMapSectorInventoryPoolHighLights( void ) void HandleMapSectorInventory( void ) { // handle mouse in compatable item map sectors inventory - HandleMouseInCompatableItemForMapSectorInventory( iCurrentlyHighLightedItem ); + // HEADROCK HAM 5: This shouldn't fire when doing Inventory Zoom + if (!fWaitingForZoomInput) + { + HandleMouseInCompatableItemForMapSectorInventory( iCurrentlyHighLightedItem ); + } return; } @@ -2231,7 +3098,16 @@ BOOLEAN IsMapScreenWorldItemVisibleInMapInventory( WORLDITEM *pWorldItem ) pWorldItem->object.usItem != ACTION_ITEM && pWorldItem->object[0]->data.bTrap <= 0 ) { - return( TRUE ); + // HEADROCK HAM 5: Map Inventory Filters are applied here. The item in question + // has to match one of the Item Classes defined in this global Flags variable. + if (Item[pWorldItem->object.usItem].usItemClass & guiMapInventoryFilter) + { + return ( TRUE ); + } + else + { + return( FALSE ); + } } return( FALSE ); @@ -2420,7 +3296,7 @@ BOOLEAN LoadInventoryPoolQ (UINT8 ubSaveGameID) INT32 i, j, iTotNumSlots; CHAR8 tmpbuf[1024]; UINT32 uiNumBytesRead; - HWFILE hFile; + HWFILE hFile = 0; if(MAP_INVENTORY_POOL_SLOT_COUNT <= 0) { @@ -2435,9 +3311,14 @@ BOOLEAN LoadInventoryPoolQ (UINT8 ubSaveGameID) ret = FALSE; CreateSavedGameFileNameFromNumber(ubSaveGameID, tmpbuf); strcat(tmpbuf, ".IPQ"); - hFile = FileOpen(tmpbuf, FILE_ACCESS_READ|FILE_OPEN_EXISTING, FALSE); + + //MM: This check is sometimes required while debugging a save game from release mode + if (FileExists(tmpbuf)) + hFile = FileOpen(tmpbuf, FILE_ACCESS_READ|FILE_OPEN_EXISTING, FALSE); + if(hFile == 0) return(TRUE); + MemFreeInventoryPoolQ(); FileRead(hFile, iCurrentInventoryPoolPageQ+1, (INVPOOLLISTNUM-1)*sizeof(INT32), &uiNumBytesRead); if(uiNumBytesRead != (INVPOOLLISTNUM-1)*sizeof(INT32)) @@ -2693,13 +3574,26 @@ void DeleteItemsOfType( UINT16 usItemType ) } extern UINT8 CurrentPlayerProgressPercentage(void); -INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier ) + +// HEADROCK HAM 5: Added argument +INT32 SellItem( OBJECTTYPE& object, BOOLEAN fAll, BOOLEAN useModifier ) { INT32 iPrice = 0; INT16 iPriceModifier = gGameExternalOptions.iPriceModifier; UINT16 usItemType = object.usItem; UINT16 itemPrice = Item[usItemType].usPrice; + // HEADROCK HAM 5: Controls whether we price the top object or all objects in the stack. + UINT8 ubNumberOfObjects; + if (!fAll) + { + ubNumberOfObjects = 1; + } + else + { + ubNumberOfObjects = object.ubNumberOfObjects; + } + //CHRISL: make price modifier based on current game progress if(iPriceModifier == 0) { @@ -2743,7 +3637,8 @@ INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier ) { if(pLBE->inv[x].exists() == true) { - iPrice += SellItem(pLBE->inv[x], FALSE); + // HEADROCK HAM 5: Added argument + iPrice += SellItem(pLBE->inv[x], TRUE, FALSE); } } } @@ -2751,7 +3646,10 @@ INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier ) iPrice += ( itemPrice * object[ubLoop]->data.objectStatus / 100 ); for (attachmentList::iterator iter = object[ubLoop]->attachments.begin(); iter != object[ubLoop]->attachments.end(); ++iter) { if(iter->exists()) - iPrice += SellItem(*iter, FALSE); + { + // HEADROCK HAM 5: Added argument + iPrice += SellItem(*iter, TRUE, FALSE); + } } } } @@ -2763,7 +3661,10 @@ INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier ) iPrice += ( itemPrice * object[ubLoop]->data.objectStatus / 100 ); for (attachmentList::iterator iter = object[ubLoop]->attachments.begin(); iter != object[ubLoop]->attachments.end(); ++iter) { if(iter->exists()) - iPrice += SellItem(*iter, FALSE); + { + // HEADROCK HAM 5: Added argument + iPrice += SellItem(*iter, TRUE, FALSE); + } } } @@ -2775,3 +3676,1335 @@ INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier ) return iPrice; } + +// HEADROCK HAM 5: This function handles altering the coordinates and sizes of all map inventory slots. This is used +// for a switch between Small and Large inventory item display ("zoom"). +void ResetMapInventoryOffsets( void ) +{ + if ( !fMapInventoryZoom ) + { + ////////////////////////////////////////////// + // ZOOM OFF + + if (iResolution >= _640x480 && iResolution < _800x600) + { + MAP_INV_SLOT_COLS = 8; + MAP_INVENTORY_POOL_SLOT_START_X = 269; + MAP_INVENTORY_POOL_SLOT_START_Y = 51; + } + else if (iResolution >= _800x600 && iResolution < _1024x768) + { + MAP_INV_SLOT_COLS = 11; + MAP_INVENTORY_POOL_SLOT_START_X = 278; + MAP_INVENTORY_POOL_SLOT_START_Y = 62; + } + else if (iResolution >= _1024x768) + { + MAP_INV_SLOT_COLS = 17; + MAP_INVENTORY_POOL_SLOT_START_X = 282; + MAP_INVENTORY_POOL_SLOT_START_Y = 50; + } + MAP_INVENTORY_POOL_SLOT_COUNT = GetInventorySlotCount( FALSE ); + + ITEMDESC_ITEM_STATUS_HEIGHT_INV_POOL = 20; + ITEMDESC_ITEM_STATUS_WIDTH_INV_POOL = 2; + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_X = 5; + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y = 22; + + ITEMDESC_ITEM_NAME_POOL_OFFSET_Y = 3; + + MAP_INVEN_SLOT_WIDTH = 65; + MAP_INVEN_SPACE_BTWN_SLOTS = 72; + MAP_INVEN_SLOT_HEIGHT = 32; + MAP_INVEN_SLOT_IMAGE_HEIGHT = 24; + + MAP_INVEN_NAME_FONT = SMALLCOMPFONT; + + } + else + { + //////////////////////////////////// + // ZOOM ON + + if (iResolution >= _640x480 && iResolution < _800x600) + { + MAP_INV_SLOT_COLS = 3; + MAP_INVENTORY_POOL_SLOT_START_X = 269; + MAP_INVENTORY_POOL_SLOT_START_Y = 51; + } + else if (iResolution >= _800x600 && iResolution < _1024x768) + { + MAP_INV_SLOT_COLS = 5; + MAP_INVENTORY_POOL_SLOT_START_X = 278; + MAP_INVENTORY_POOL_SLOT_START_Y = 62; + } + else if (iResolution >= _1024x768) + { + MAP_INV_SLOT_COLS = 8; + MAP_INVENTORY_POOL_SLOT_START_X = 282; + MAP_INVENTORY_POOL_SLOT_START_Y = 50; + } + + MAP_INVENTORY_POOL_SLOT_COUNT = GetInventorySlotCount( TRUE ); + ITEMDESC_ITEM_STATUS_HEIGHT_INV_POOL = 40; + ITEMDESC_ITEM_STATUS_WIDTH_INV_POOL = 3; + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_X = 5; + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y = 44; + + ITEMDESC_ITEM_NAME_POOL_OFFSET_Y = 10; + + MAP_INVEN_SLOT_WIDTH = 129; + MAP_INVEN_SPACE_BTWN_SLOTS = 135; + MAP_INVEN_SLOT_HEIGHT = 67; + MAP_INVEN_SLOT_IMAGE_HEIGHT = 49; + + MAP_INVEN_NAME_FONT = FONT10ARIAL; + } +} + +// HEADROCK HAM 5: Load the BigItem graphics for all items on this page of the sector inventory. +// Because BigItem graphics are not kept in memory by default, we need to load them now in order to display all items +// on the screen. Note that this function will run every time the contents of the inventory are changed, in order to +// ensure that new items now appearing on the screen have their BigItem pics accessible. +void LoadAllMapInventoryBigItemGraphics() +{ + INT32 iCounter = 0; + + // Go through each and every slot that exists on the screen. + for( iCounter = 0; iCounter < MAP_INVENTORY_POOL_SLOT_COUNT ; iCounter++ ) + { + LoadMapInventoryBigItemGraphic( iCounter ); + } +} + +// HEADROCK HAM 5: This function loads a specific BigItem graphic for one of the items in the Sector Inventory. +// This is done because BigItem graphics are not stored in memory - this function allows us to access one of them, +// using only its position in the inventory. +void LoadMapInventoryBigItemGraphic( INT32 iCounter ) +{ + // Get the item's absolute position in the sector inventory + INT32 iItemNum = iCounter + (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + UINT32 uiItemGraphic; + // Does the item exist? (If not, it's an empty slot and requires no graphics) + if (pInventoryPoolList[ iItemNum ].object.exists()) + { + // Load the graphic from the STI + LoadTileGraphicForItem( &(Item[ pInventoryPoolList[ iItemNum ].object.usItem ]), &uiItemGraphic ); + // Record the number of graphic used, so we can easily access it later. The array contains only images + // for the items currently on the screen, so [0] is always the item in the top-left corner. + giMapInventoryBigItemGraphics[ iCounter ].iGraphicNum = (INT32)uiItemGraphic; + giMapInventoryBigItemGraphics[ iCounter ].usItem = pInventoryPoolList[ iItemNum ].object.usItem; + } + else + { + // Empty slot. Reset the number so we don't try to unload the picture here. + giMapInventoryBigItemGraphics[ iCounter ].iGraphicNum = -1; + giMapInventoryBigItemGraphics[ iCounter ].usItem = 0; + } +} + +// HEADROCK HAM 5: Unload all BigItem graphics from memory. +// We run this every time the zoomed inventory (or the entire inventory) is closed. +void UnloadAllMapInventoryBigItemGraphics() +{ + for (INT32 iCounter = 0; iCounter < MAP_INVENTORY_POOL_MAX_SLOTS; iCounter++) + { + UnloadMapInventoryBigItemGraphic( iCounter ); + } +} + +void UnloadMapInventoryBigItemGraphic( INT32 iCounter ) +{ + // Skip slots that have no item graphic assigned to them. + if (giMapInventoryBigItemGraphics [ iCounter ].iGraphicNum > -1) + { + DeleteVideoObjectFromIndex( (UINT32)giMapInventoryBigItemGraphics[ iCounter ].iGraphicNum ); + giMapInventoryBigItemGraphics[ iCounter ].iGraphicNum = -1; + giMapInventoryBigItemGraphics[ iCounter ].usItem = 0; + } +} + +void ResetAllMapInventoryBigItemGraphics() +{ + for (INT32 iCounter = 0; iCounter < MAP_INVENTORY_POOL_MAX_SLOTS; iCounter++) + { + giMapInventoryBigItemGraphics[ iCounter ].iGraphicNum = -1; + giMapInventoryBigItemGraphics[ iCounter ].usItem = 0; + } +} + +// HEADROCK HAM 5: Handle switch to Zoomed inventory on given page. +void HandleMapInventoryZoom( UINT32 iPage, INT32 iCounter ) +{ + // Set the zoom flag. This tells the program to draw all items using their large images, and also ensures + // that all coordinates are set to show large slots. + fMapInventoryZoom = 1; + + // Erase all of these flags to stop items from being outlined. + for (UINT16 x = 0; x < MAP_INVENTORY_POOL_MAX_SLOTS; x++) + { + gfMapInventoryItemToZoom[x] = FALSE; + } + + // Before resetting the pool slots, lets determine the position and size of the slot we're about to + // zoom into. We'll use this for animations. + INT32 iStartX = (INT32)( MAP_INVENTORY_POOL_SLOT_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCounter / MAP_INV_SLOT_COLS ) ) ); + INT32 iStartY = (INT32)( MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCounter % ( MAP_INV_SLOT_COLS ) ) ) ); + UINT32 iOrigWidth = MAP_INVEN_SLOT_WIDTH; + UINT32 iOrigHeight = MAP_INVEN_SLOT_HEIGHT; + UINT16 iLocationInPool = iCounter + (iCurrentInventoryPoolPage * MAP_INVENTORY_POOL_SLOT_COUNT); + + // Destroy all pool slots... + DestroyMapInventoryPoolSlots(); + // Reinit the coordinates and sizes for the larger inventory... + ResetMapInventoryOffsets(); + // Recreate all pool slots... + CreateMapInventoryPoolSlots( ); + + // Set current page + iCurrentInventoryPoolPage = iPage; + + // Load all BigItem graphics for the current page. + ResetAllMapInventoryBigItemGraphics(); + + AnimateZoomInventory ( iLocationInPool, iCounter, iStartX, iStartY, iOrigWidth, iOrigHeight ); + + ResizeInventoryList(); + + // Flag as dirty for re-render. + fMapPanelDirty = TRUE; +} + +// HEADROCK HAM 5: Handle switch to Unzoomed inventory on given page. +void HandleMapInventoryUnzoom( UINT32 iPage ) +{ + // Reset zoom flag + fMapInventoryZoom = 0; + + giDesiredNumMapInventorySlots = -1; + + // Load all BigItem graphics for the current page. + UnloadAllMapInventoryBigItemGraphics(); + + // Destroy all pool slots, reinit all inventory coordinates and variables, then recreate slots. + DestroyMapInventoryPoolSlots(); + ResetMapInventoryOffsets(); + CreateMapInventoryPoolSlots( ); + + // Set current page + iCurrentInventoryPoolPage = iPage; + + // Flag as dirty for re-render. + fMapPanelDirty = TRUE; +} + +// HEADROCK HAM 5: This function tells us how many slots there will be in the zoomed inventory. We run this +// both when creating the zoomed inventory, as well as when trying to calculate which items from unzoomed mode +// will appear in zoomed mode. +UINT16 GetInventorySlotCount( BOOLEAN fZoomed ) +{ + if (!fZoomed) + { + if (iResolution >= _640x480 && iResolution < _800x600) + { + return 40; + } + if (iResolution >= _800x600 && iResolution < _1024x768) + { + return 77; + } + if (iResolution >= _1024x768) + { + return MAP_INVENTORY_POOL_MAX_SLOTS; + } + } + else + { + if (iResolution >= _640x480 && iResolution < _800x600) + { + return 6; + } + if (iResolution >= _800x600 && iResolution < _1024x768) + { + return 15; + } + if (iResolution >= _1024x768) + { + return 40; + } + } + + // Error! + AssertMsg( 0, "Zoomed Inventory error: Encountered unknown resolution setting." ); + return 0; +} + +// HEADROCK HAM 5: This is a callback function that will cancel inventory zoom input. +void CancelInventoryZoomInput( BOOLEAN fButtonOff ) +{ + // Are we being asked to turn the Zoom Button off? + if (fButtonOff) + { + // Do it by referencing the button and toggling its flag manually. + ButtonList[ guiMapInvenZoomButton ]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Erase all of these flags to stop items from being outlined. + for (UINT16 x = 0; x < MAP_INVENTORY_POOL_MAX_SLOTS; x++) + { + gfMapInventoryItemToZoom[x] = FALSE; + } + + // Reset flag to inform the program that we are no longer expecting zoom input. Slot Mouse-Regions will now + // behave normally. + fWaitingForZoomInput = 0; + // Free up the mouse! + FreeMouseCursor(); +} + +// HEADROCK HAM 5: This function handles animation from Unzoomed inventory mode to Zoomed inventory mode. +// The animation takes an entire "slot" from the inventory view, and moves and stretches it to the exact +// location and dimensions it'll have in "zoomed" mode. This is similar to the zooming of the Laptop transition. +void AnimateZoomInventory ( UINT16 iLocationInPool, UINT16 iCounter, INT32 iStartX, INT32 iStartY, UINT32 uiOrigWidth, UINT32 uiOrigHeight ) +{ + // We get here after already having set the offsets for the zoomed mode. We use one now to determine + // the position of our slot within the zoomed page - which will open as soon as this animation is done. + // Therefore, we're getting the animation's destination coordinates. + UINT16 iCounterZoomed = iLocationInPool % MAP_INVENTORY_POOL_SLOT_COUNT; + + // Declarations + SGPRect SrcRect, CurRect, StartRect, DstRect; + INT32 iPercentage; + UINT32 uiStartTime, uiTimeRange, uiCurrTime; + INT32 iX, iY, iWidth, iHeight; + INT16 sCenX, sCenY, usWidth, usHeight, sX, sY; + INT16 sWidth = 0, sHeight = 0; + ETRLEObject *pTrav; + HVOBJECT hHandle; + INT32 iRealPercentage; + + // Make the mouse cursor disappear for now. It'll automatically restore itself when the animation is done. + // Disabled for now... + //SetCurrentCursorFromDatabase( VIDEO_NO_CURSOR ); + + // First lets copy the entire screen into the SAVEBUFFER. By doing this we can ensure that the background + // gets redrawn under our moving sprite. + BlitBufferToBuffer( FRAME_BUFFER, guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); + // For extra "cool" effect, we'll be making one slot "disappear", by blitting a part of the inventory + // pool graphic on top of it. This'll give the impression that our slot is actually moving away from its + // original location, leaving a hole behind it! + // Get the pool graphic... + GetVideoObject(&hHandle, guiMapInventoryPoolBackground); + // Blit to the EXTRABUFFER + BltVideoObject( guiEXTRABUFFER , hHandle, 0,INVEN_POOL_X, INVEN_POOL_Y , VO_BLT_SRCTRANSPARENCY,NULL ); + // Copy a piece of it back into the frame. + BlitBufferToBuffer( guiEXTRABUFFER, guiSAVEBUFFER, iStartX, iStartY, uiOrigWidth, uiOrigHeight ); + + // Not really necessary, but I'm not sure if I should remove it. + RestoreBackgroundRects(); + + // STEP 1: We make our sprite. This is done by drawing a slot to the top-left corner of the EXTRABUFFER, + // then drawing our item on it. We'll be copying it back with every frame, at a new destination and + // size. + + // Draw a big-item slot to 0,0. + GetVideoObject(&hHandle, guiMapInventoryPoolSlot); + sX = 0; + sY = 0; + BltVideoObject( guiEXTRABUFFER , hHandle, 1, sX, sY , VO_BLT_SRCTRANSPARENCY,NULL ); + + // Does the object exist? + if (pInventoryPoolList[ iLocationInPool ].object.exists() ) + { + // We draw the BigItem graphic on top of the slot. + + // Get the BigItem Graphic for the item. + LoadMapInventoryBigItemGraphic( iCounterZoomed ); + GetVideoObject( &hHandle, (UINT32)giMapInventoryBigItemGraphics[ iCounterZoomed ].iGraphicNum ); + // Calculate size + pTrav = &( hHandle->pETRLEObject[ 0 ] ); + usHeight = (UINT16)pTrav->usHeight; + usWidth = (UINT16)pTrav->usWidth; + // Mandatory offsets... + sX = 5; + sY = 1; + // Find the X,Y for the BigItem Image, so that it would center on the slot. + sCenX = sX + ( abs(MAP_INVEN_SLOT_WIDTH - usWidth) / 2 ) - pTrav->sOffsetX; + sCenY = sY + ( abs(MAP_INVEN_SLOT_IMAGE_HEIGHT - usHeight) / 2 ) - pTrav->sOffsetY; + // Blit to the EXTRABUFFER + BltVideoObject( guiEXTRABUFFER , hHandle, 0, sCenX, sCenY , VO_BLT_SRCTRANSPARENCY,NULL ); + + // We print the item name below the item graphic + CHAR16 sString[ 64 ]; + wcscpy( sString, ShortItemNames[ pInventoryPoolList[ iLocationInPool ].object.usItem ] ); + if( StringPixLength( sString, MAP_INVEN_NAME_FONT ) >= ( MAP_INVEN_SLOT_WIDTH ) ) + { + ReduceStringLength( sString, ( INT16 )( MAP_INVEN_SLOT_WIDTH - StringPixLength( L" ...", MAP_INVEN_NAME_FONT ) ), MAP_INVEN_NAME_FONT ); + } + INT16 iFontWidth = 0; + INT16 iFontHeight = 0; + FindFontCenterCoordinates( 2, + 0, MAP_INVEN_SLOT_WIDTH, 0, + sString, MAP_INVEN_NAME_FONT, + &iFontWidth, &iFontHeight ); + + // Blit to the EXTRABUFFER + SetFontDestBuffer( guiEXTRABUFFER, 0,0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); + SetFont( MAP_INVEN_NAME_FONT ); + SetFontForeground( FONT_WHITE ); + SetFontBackground( FONT_BLACK ); + + mprintf( iFontWidth, ITEMDESC_ITEM_NAME_POOL_OFFSET_Y + ITEMDESC_ITEM_STATUS_INV_POOL_OFFSET_Y, sString ); + } + + // Set the size of the EXTRABUFFER region we want to copy and use as a sprite. + SrcRect.iLeft = 0; + SrcRect.iTop = 0; + SrcRect.iRight = MAP_INVEN_SLOT_WIDTH; + SrcRect.iBottom = MAP_INVEN_SLOT_HEIGHT; + + // STEP 2: The inventory is now stored in the SAVEBUFFER, and our sprite is in the EXTRABUUFFER. + // Lets calculate the position and size of the sprite at the start of the animation and at its end. + + // Set the size of the sprite when it starts moving. Topleft should be the top left corner of the slot in + // the unzoomed inventory, width and height correspond to the size of an unzoomed slot. + // We get this info from outside the function, because we can't calculate it now that the offsets have + // already been changed. + StartRect.iLeft = iStartX; + StartRect.iTop = iStartY; + StartRect.iRight = iStartX + uiOrigWidth; + StartRect.iBottom = iStartY + uiOrigHeight; + + // The destination is the top left corner of the slot in zoomed view. Its height and width are defined by + // the size of a zoomed slot. Again, since we've already altered all the offsets, we can use them now to calculate + // that position and size. + DstRect.iLeft = (INT32)( MAP_INVENTORY_POOL_SLOT_OFFSET_X + MAP_INVENTORY_POOL_SLOT_START_X + ( ( MAP_INVEN_SPACE_BTWN_SLOTS ) * ( iCounterZoomed / MAP_INV_SLOT_COLS ) ) ); + DstRect.iTop = (INT32)( MAP_INVENTORY_POOL_SLOT_START_Y + ( ( MAP_INVEN_SLOT_HEIGHT ) * ( iCounterZoomed % ( MAP_INV_SLOT_COLS ) ) ) ); + DstRect.iRight = DstRect.iLeft + MAP_INVEN_SLOT_WIDTH; + DstRect.iBottom = DstRect.iTop + MAP_INVEN_SLOT_HEIGHT; + + // Calculate Deltas + + // Movement of the sprite's top-left corner + INT32 iDeltaX = DstRect.iLeft - StartRect.iLeft; + INT32 iDeltaY = DstRect.iTop - StartRect.iTop; + // Change in width and height + INT32 iDeltaWidth = (DstRect.iRight - DstRect.iLeft) - (StartRect.iRight - StartRect.iLeft); + INT32 iDeltaHeight = (DstRect.iBottom - DstRect.iTop) - (StartRect.iBottom - StartRect.iTop); + + // Set transition time and other variables. + uiTimeRange = 400; + iPercentage = iRealPercentage = 0; + uiStartTime = GetJA2Clock(); + + // Loud click! + PlayJA2Sample( 202, RATE_11025, HIGHVOLUME, 1, MIDDLEPAN ); + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // ANIMATE + + while( iPercentage < 100 ) + { + // The animation process is quite simple, and is done in a more simple fashion than the Laptop transition + // as well - though the principle is the same. + // We first blit the background into the frame buffer to draw it on the screen. Then we calculate the current + // size and position of the sprite, and blit it to the frame buffer as well. + // By tracking the time on the clock we can figure out where the sprite should be at any given time, + // how large it should be, and also use effects to make it look cooler. + + // First of all, blit the Map Inventory back from the SAVEBUFFER into the FRAMEBUFFER, so that it gets + // drawn on the screen as a background. We don't need to draw the entire screen - at least not yet... + BlitBufferToBuffer( guiSAVEBUFFER, FRAME_BUFFER, INVEN_POOL_X, INVEN_POOL_Y, SCREEN_WIDTH-INVEN_POOL_X, SCREEN_HEIGHT-INVEN_POOL_Y); + + // Get the current time + uiCurrTime = GetJA2Clock(); + // Compare it to the current time to get a percentage. This signifies how much of the animation has been + // done. + iPercentage = (uiCurrTime-uiStartTime) * 100 / uiTimeRange; + // Can't be more than 100%, duh. + iPercentage = min( iPercentage, 100 ); + + // To make a "falling" effect, we bias the percentage. I'm not sure how the maths work, but they do. + INT32 iScalePercentage = iPercentage; + INT32 iFactor = (iScalePercentage - 50) * 2; + if( iScalePercentage < 50 ) + iScalePercentage = (UINT32)(iScalePercentage + iScalePercentage * iFactor * 0.01 + 0.5); + else + iScalePercentage = (UINT32)(iScalePercentage + (100-iScalePercentage) * iFactor * 0.01 + 0.5); + + // Find the width and height the sprite at this time. + iWidth = (INT32)(uiOrigWidth + (INT32)(( iDeltaWidth * iScalePercentage ) / 100)); + iHeight = (INT32)(uiOrigHeight + (INT32)(( iDeltaHeight * iScalePercentage ) / 100)); + // Find the sprite's current X,Y coordinates + iX = (INT32)(iStartX + (INT32)(( iDeltaX * iScalePercentage ) / 100)); + iY = (INT32)(iStartY + (INT32)(( iDeltaY * iScalePercentage ) / 100)); + + // Set the position and size of the rectangle on which we'll draw this sprite. + CurRect.iLeft = iX; + CurRect.iRight = iX + iWidth; + CurRect.iTop = iY; + CurRect.iBottom = iY + iHeight; + + // Stretch the image of the sprite, still stored in the EXTRABUFFER, to its correct + // size and position on the FRAMEBUFFER, essentially drawing it on the screen on top of + // the background. + BltStretchVideoSurface( FRAME_BUFFER, guiEXTRABUFFER, 0, 0, 0, &SrcRect, &CurRect ); + + // Something.... + InvalidateScreen(); + + // Some other thing... + RefreshScreen( NULL ); + } + // Unload the image of the item we just animated. Yeah, we'll be using it in two seconds for drawing + // the zoomed inventory page, but it's safer if it doesn't exist in memory anymore. I'm not good enough + // to figure out whether this is necessary :) + UnloadMapInventoryBigItemGraphic( iCounterZoomed ); + + // Dirty! + fMapPanelDirty=TRUE; +} + +// HEADROCK HAM 5: This function sorts all ammo items in the inventory into crates. It is the same operation +// performed when pressing CTRL+SHIFT+A in the Tactical screen, except it works directly with the sector +// inventory. Most of the function was ripped from that function - with several significant changes. For one, +// the program never looks at any mercs' crates-in-hand. +void SortSectorInventoryAmmo(bool useBoxes) +{ + + // Declarations + INT32 crateItem; + bool mergeSuccessful, ammoPresent; + OBJECTTYPE newCrate; + int loopCount = 0; + + SOLDIERTYPE * pSoldier = &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]); + + AssertMsg( pSoldier != NULL, "Sector Inventory: Attempting ammo sort without valid selected soldier?" ); + + //MM: added for ammo boxes + ammoPresent = true; + UINT8 magType = AMMO_CRATE; + if (useBoxes) + magType = AMMO_BOX; + + // Get the original size of the inventory. + UINT32 uiOrigInvSize = pInventoryPoolList.size(); + + while (ammoPresent && loopCount <= 10 ) + { + // Start scanning through the entire Sector Inventory pool. + for(UINT32 iInvCounter = 0; iInvCounter < uiOrigInvSize; iInvCounter++) + { + // Set values + crateItem = 0; + mergeSuccessful = false; + + // Skip objects that don't exist (empty slots) + if(pInventoryPoolList[iInvCounter].object.exists() == false) + { + continue; + } + + // Store a pointer to this object and record its item number + OBJECTTYPE *pCurObject = &(pInventoryPoolList[iInvCounter].object); + UINT16 usCurItem = pCurObject->usItem; + + // If the object.... + if(Item[usCurItem].usItemClass & IC_AMMO && // Is an ammo item + Magazine[Item[usCurItem].ubClassIndex].ubMagType != magType && // MM: Is not current crate/box selection + pInventoryPoolList[iInvCounter].usFlags & WORLD_ITEM_REACHABLE && // Is reachable + !(pInventoryPoolList[iInvCounter].usFlags & WORLD_ITEM_ARMED_BOMB) ) // Is not boobytrapped! + { + // We have a valid, ammo item - one or more magazines. We'll want to dump as much ammo from them + // as possible into crates. + + // Look through all items in the game to try and find an ammocrate that can contain this kind of ammo. + for(int iCrateLoop = 0; iCrateLoop < MAXITEMS; iCrateLoop++) + { + // Is it the right ammo crate? + if( Item[iCrateLoop].usItemClass == IC_AMMO && + Magazine[Item[iCrateLoop].ubClassIndex].ubMagType == magType && // An ammo crate or box + Magazine[Item[iCrateLoop].ubClassIndex].ubCalibre == Magazine[Item[usCurItem].ubClassIndex].ubCalibre && //Same caliber + Magazine[Item[iCrateLoop].ubClassIndex].ubAmmoType == Magazine[Item[usCurItem].ubClassIndex].ubAmmoType ) // Same ammotype + { + // Found a crate for this ammo. + crateItem = iCrateLoop; + break; + } + } + + // Have we found a crate? + if(crateItem != 0) + { + // Excellent. Let see if a crate like this already exists in the Sector Inventory. + for(UINT32 iInvCounter2=0; iInvCounter2 < pInventoryPoolList.size(); iInvCounter2++) + { + // Non-empty slots please... + if(pInventoryPoolList[iInvCounter2].object.exists() == true) + { + // Is it our crate item? + if (pInventoryPoolList[iInvCounter2].object.usItem == crateItem) + { + // Try pouring our ammo into this crate. + DistributeStatus(pCurObject, &(pInventoryPoolList[iInvCounter2].object), Magazine[Item[crateItem].ubClassIndex].ubMagSize); + // If the ammo magazine disappeared, that means we were successful in passing all + // ammo from it to the crate. + if(pCurObject->ubNumberOfObjects < 1) + { + mergeSuccessful = true; + // Delete the magazine, it's empty anyway. + DeleteObj( pCurObject ); + break; + } + // If we've reched this point, then we've still got ammo to distribute. Keep + // looking through the inventory for additional crates to dump it into. + } + } + } + // Did we find enough crates and dumped all our ammo into them? + if(mergeSuccessful == false) + { + // Hmm, so we need to create a new crate. + CreateAmmo(crateItem, &newCrate, 0); + // Dump all the ammo into it. + DistributeStatus(pCurObject, &newCrate, Magazine[Item[crateItem].ubClassIndex].ubMagSize); + // Place it in the sector inventory. + AutoPlaceObjectToWorld( pSoldier, &newCrate, true ); + // Ran out of magazines? + if(pCurObject->ubNumberOfObjects < 1) + { + // Excellent, we're done. + mergeSuccessful = true; + // Delete this magazine item. + DeleteObj( pCurObject ); + } + } + } + } + } + + // if we added to / created a box/crate, then we're fine to reset this + if ( mergeSuccessful ) + loopCount = 0; + + //MM: loop through ammo multiple times, as boxes and crates may take a few passes to fill + ammoPresent = false; + for(unsigned int i = 0; i < pInventoryPoolList.size(); i++) + { + if(Item[pInventoryPoolList[i].object.usItem].usItemClass == IC_AMMO && pInventoryPoolList[i].bVisible == TRUE && pInventoryPoolList[i].fExists && (pInventoryPoolList[i].usFlags & WORLD_ITEM_REACHABLE) && !(pInventoryPoolList[i].usFlags & WORLD_ITEM_ARMED_BOMB)) + { + if(Magazine[Item[pInventoryPoolList[i].object.usItem].ubClassIndex].ubMagType == magType) + continue; + + loopCount++; + ammoPresent = true; + break; + } + } + + SortSectorInventoryStackAndMerge(true); + } + // FINISHED + CHAR16 pStr[500]; + swprintf( pStr, gzMapInventorySortingMessage[ 0 ], 'A' + sSelMapY - 1, sSelMapX ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pStr ); + + // Dirty! + fMapPanelDirty=TRUE; +} + +// HEADROCK HAM 5: This function removed all ammunition from each item in the sector inventory. It is the same +// as the SHIFT+F function, except it deals directly with the sector inventory pool rather than the WorldItems list. +void SortSectorInventoryEjectAmmo() +{ + OBJECTTYPE gTempObject; + + SOLDIERTYPE * pSoldier = &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]); + + for ( UINT32 uiLoop = 0; uiLoop < pInventoryPoolList.size(); uiLoop++ ) //for all items in sector + { + if ( pInventoryPoolList[uiLoop].bVisible == TRUE && // Visible + pInventoryPoolList[uiLoop].object.exists() == true && // Exists + pInventoryPoolList[uiLoop].usFlags & WORLD_ITEM_REACHABLE && // Reachable + !(pInventoryPoolList[uiLoop].usFlags & WORLD_ITEM_ARMED_BOMB) ) // Not booby-trapped! + { + if (Item[ pInventoryPoolList[uiLoop].object.usItem ].usItemClass & IC_GUN) // Is a gun? + { + // Iterate through stacks + for (int x = 0; x < pInventoryPoolList[uiLoop].object.ubNumberOfObjects; ++x) + { + //Remove magazine + if ( (pInventoryPoolList[uiLoop].object[x]->data.gun.usGunAmmoItem != NONE) && (pInventoryPoolList[uiLoop].object[x]->data.gun.ubGunShotsLeft > 0) ) + { + CreateAmmo(pInventoryPoolList[uiLoop].object[x]->data.gun.usGunAmmoItem, &gTempObject, pInventoryPoolList[uiLoop].object[x]->data.gun.ubGunShotsLeft); + pInventoryPoolList[uiLoop].object[x]->data.gun.ubGunShotsLeft = 0; + pInventoryPoolList[uiLoop].object[x]->data.gun.usGunAmmoItem = NONE; + // HEADROCK HAM 3.5: Clear ammo type + pInventoryPoolList[uiLoop].object[x]->data.gun.ubGunAmmoType = NONE; + + // put it on the ground + AutoPlaceObjectToWorld( pSoldier, &gTempObject, true ); + + // Check... + if (&gTempObject != NULL) + { + DeleteObj( &gTempObject ); + } + } + } + } + } + } + // "FINISHED" + CHAR16 pStr[500]; + swprintf( pStr, gzMapInventorySortingMessage[ 2 ], 'A' + sSelMapY - 1, sSelMapX ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pStr ); + + // Dirty! + fMapPanelDirty=TRUE; +} + +void SortSectorInventorySeparateAttachments() +{ + + OBJECTTYPE gTempObject; + + SOLDIERTYPE * pSoldier = &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]); + + for ( UINT32 uiLoop = 0; uiLoop < pInventoryPoolList.size(); uiLoop++ ) //for all items in sector + { + if ( pInventoryPoolList[uiLoop].bVisible == TRUE && // Visible + pInventoryPoolList[uiLoop].object.exists() == true && // Exists + pInventoryPoolList[uiLoop].usFlags & WORLD_ITEM_REACHABLE && // Reachable + !(pInventoryPoolList[uiLoop].usFlags & WORLD_ITEM_ARMED_BOMB) ) // Not booby-trapped! + { + + // Iterate through stacks + for (int x = 0; x < pInventoryPoolList[uiLoop].object.ubNumberOfObjects; ++x) + { + UINT8 cnt = 0, uiLoopCnt = 0; + + while(pInventoryPoolList[uiLoop].object[x]->attachments.size() != cnt) + { + gTempObject = *(pInventoryPoolList[uiLoop].object[x]->GetAttachmentAtIndex(cnt)); + + //WarmSteel - This actually still works with NAS, be it by accident + if (pInventoryPoolList[uiLoop].object.RemoveAttachment(&gTempObject,0,x)) + { + AutoPlaceObjectToWorld( pSoldier, &gTempObject, true ); + if (&gTempObject != NULL) + { + DeleteObj( &gTempObject ); + } + } + else + { + cnt++; + } + + uiLoopCnt ++; + + // Failsafe + if(uiLoopCnt > 100) + { + break; + } + } + } + } + } + // "FINISHED" + CHAR16 pStr[500]; + swprintf( pStr, gzMapInventorySortingMessage[ 1 ], 'A' + sSelMapY - 1, sSelMapX ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pStr ); + + // Dirty! + fMapPanelDirty=TRUE; +} + +// HEADROCK HAM 5: This function sorts all inventory items into stacks. It is similar to a hotkey-driven +// function that normally runs only in tactical mode, except it has been altered to work directly with the +// sector inventory pool. +void SortSectorInventoryStackAndMerge(bool ammoOnly ) +{ + OBJECTTYPE * StackObject; + + SOLDIERTYPE * pSoldier = &(Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ]); + + // Run through sector inventory. + for ( UINT32 uiLoop = 0; uiLoop < pInventoryPoolList.size(); uiLoop++ ) + { + if ( pInventoryPoolList[ uiLoop ].bVisible == TRUE && // Visible + pInventoryPoolList[ uiLoop ].object.exists() == true && // Exists + pInventoryPoolList[ uiLoop ].usFlags & WORLD_ITEM_REACHABLE && // Reachable + !(pInventoryPoolList[ uiLoop ].usFlags & WORLD_ITEM_ARMED_BOMB) ) // Not boobytrapped! + { + if ( ammoOnly && Item[pInventoryPoolList[uiLoop].object.usItem].usItemClass != IC_AMMO ) + continue; + + // Set this as the stack into which we'll try to pool all other items of this type. + StackObject = &(pInventoryPoolList[uiLoop].object); + UINT16 usStackItem = StackObject->usItem; + + // Check whether there are any other objects of the same type that we can add to this stack. + // We run only forward through the inventory, thus skipping any stacks that have already been checked. + for(UINT32 i = uiLoop+1; i < pInventoryPoolList.size(); i++) + { + // Did we encounter a valid item of the same type? + if(pInventoryPoolList[i].object.usItem == usStackItem && // Same type + pInventoryPoolList[i].bVisible == TRUE && // Visible + pInventoryPoolList[i].object.exists() == true && // Exists + pInventoryPoolList[i].usFlags & WORLD_ITEM_REACHABLE && // Reachable + !(pInventoryPoolList[i].usFlags & WORLD_ITEM_ARMED_BOMB) ) // Not boobytrapped! + { + // Add it to the stack! + StackObject->AddObjectsToStack(pInventoryPoolList[i].object, -1, NULL, NUM_INV_SLOTS, MAX_OBJECTS_PER_SLOT, false); + + // Have we removed all items from the secondary stack? + if( pInventoryPoolList[i].object.exists() == false ) + { + // Destroy it. + DeleteObj( &(pInventoryPoolList[i].object) ); + } + } + } + //merge items in stack + CleanUpStack(StackObject, NULL); + + // Pick up the stack, and place it back into the inventory. That will sort items back into any empty + // slots. + + // Create a new object, same as this stack. + OBJECTTYPE TempStack; + TempStack.initialize(); + TempStack = pInventoryPoolList[ uiLoop ].object; + + // Erase the original from its position in the inventory. + pInventoryPoolList[ uiLoop ].bVisible = FALSE; + pInventoryPoolList[ uiLoop ].fExists = FALSE; + DeleteObj( &(pInventoryPoolList[ uiLoop ].object) ); + + // Place the duplicate into the inventory, filling any gaps. + AutoPlaceObjectToWorld( pSoldier, &TempStack, TRUE ); + if (TempStack.exists() == true ) + { + DeleteObj( &TempStack ); + } + } + } + // "FINISHED" + CHAR16 pStr[500]; + swprintf( pStr, gzMapInventorySortingMessage[ 3 ], 'A' + sSelMapY - 1, sSelMapX ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pStr ); + + // Dirty! + fMapPanelDirty = TRUE; +} + +// HEADROCK HAM 5: This is a helper function that puts together all seen and unseen items from the two +// sector-inventory pools, and then rebuilds the pool. By changing the flags used by the function that tests +// whether an item should appear in the "SEEN" list, we can create filters to show or hide specific items. +// +// NOTE: Never run this function when the pool is closed or not yet created! +void RefreshSeenAndUnseenPools() +{ + INT32 iItemCount = 0; + + // Create a new itemlist to contain all items in the sector. This is a CPU-cheap alternative to saving them + // into the tempfile WorldItems list. + WORLDITEM *pItemsList = NULL; + // What is the number of items currently seen in the Sector Inventory? + INT32 iTotalNumberSeenItems = GetTotalNumberOfItems( ); + // The number of Unseen items is stored in a global, so lets use it to figure out how many items are in + // this sector inventory IN TOTAL. + INT32 iTotalNumItems = iTotalNumberSeenItems + uiNumberOfUnSeenItems; + + // Any items? + if( iTotalNumItems > 0 ) + { + // Point to a new array that will hold all these items. + pItemsList = new WORLDITEM[ iTotalNumItems ]; + + /////////////////// + // Copy all items from the SEEN inventory + // + // These are basically the items seen in the sector inventory view at the moment. The are stored in the + // main inventory list, the pInventoryPoolList. Lets go through each item on this list. + for( UINT32 iCounter = 0; iCounter < pInventoryPoolList.size(); iCounter++ ) + { + // Does it exist? (skip empty slots) + if( pInventoryPoolList[ iCounter ].object.exists() == true ) + { + // Copy it into the master pool. + pItemsList[ iItemCount ] = pInventoryPoolList[ iCounter ]; + + // Set it as true and existing... though I'm pretty sure this is unnecessary. + pItemsList[ iItemCount ].fExists = TRUE; + pItemsList[ iItemCount ].bVisible = TRUE; + // Count the number of items we've copied over. + iItemCount++; + } + } + ///////////////// + // Copy all items from the UNSEEN inventory + // + // Unseen items are stored in memory, and their quantity is also stored. This is a + // separate list from the main inventory. + // Originally this list only contained items which have not yet been spotted by any + // merc, including buried items etc. With filters, they now include all items that + // have been filtered out during the previous Stash Build. + for( UINT32 iCounter = 0; iCounter < uiNumberOfUnSeenItems; iCounter++ ) + { + // Skip any empty slots... though there shouldn't be any. + if (pUnSeenItems[ iCounter ].object.exists() == true ) + { + // Copy into the master list. + pItemsList[ iItemCount ] = pUnSeenItems[ iCounter ]; + + // Count the number of items we've copied over. + iItemCount++; + } + } + } + + // Clear both the SEEN and UNSEEN lists. + pInventoryPoolList.clear(); + pUnSeenItems = NULL; + + INT32 iCurItem = 0; + INT32 iNumUnseenItems = 0; + + // Do we have any items in the inventory? + if (iItemCount > 0) + { + ////////////////////////////////////////////// + // SPLIT APART + // + // We now run through our master item list, splitting the items between the new SEEN + // and UNSEEN inventories. This is where filters come into play. + + // Start with the SEEN list (the primary inventory). Make it large enough to contain + // all the items, just in case. + pInventoryPoolList.resize( iItemCount ); + + // Run through the master pool... + for (INT32 iCounter = 0; iCounter < iItemCount; iCounter++) + { + // Is the item visible? This takes into account FILTERS, which make items "invisible" + // by class. + if (IsMapScreenWorldItemVisibleInMapInventory( &pItemsList[ iCounter ] )) + { + // Add it to the SEEN list - the Sector Inventory Pool. + pInventoryPoolList[ iCurItem ] = pItemsList[ iCounter ]; + iCurItem++; + } + } + + // If we have not yet processed all the items, it means that some are invisible. + // How many UNSEEN items are there? + iNumUnseenItems = iItemCount - iCurItem; + + if( iNumUnseenItems > 0 ) + { + // Put them in the UNSEEN list. + + // Create a new UNSEEN pool of this size. + pUnSeenItems = new WORLDITEM[ iNumUnseenItems ]; + + iCurItem = 0; + + // Run through the master inventory. + for( INT32 iCounter = 0; iCounter < iItemCount; iCounter++ ) + { + // If item is NOT visible (including filters) + if( IsMapScreenWorldItemInvisibleInMapInventory( &pItemsList[ iCounter ] ) ) + { + // Add it to the UNSEEN pool. + pUnSeenItems[ iCurItem ] = pItemsList[ iCounter ]; + + iCurItem++; + } + } + } + } + + // Resize properly. This function makes sure that we have exactly as many pages as we need: + // to contain all the items, to have empty enough space at the end, AND to avoid crashes + // due to the sudden change in inventory size. + ResizeInventoryList(); + + // Store the number of unseen items in memory! + uiNumberOfUnSeenItems = iNumUnseenItems; + + // Finally, resort the inventory. + SortSectorInventory( pInventoryPoolList, pInventoryPoolList.size() ); + +} + +void CreateMapInventoryFilterMenu( ) +{ + /////////////////////////// + // CONSTRUCT POPUP MENU + /////////////////////////// + // If we already have a popup, destroy it first. This ensures we get a fresh menu each time. + if ( gfMapInventoryFilterPopupInitialized ) + { + delete(gMapInventoryFilterPopup); + gMapInventoryFilterPopup = NULL; + gfMapInventoryFilterPopupInitialized = FALSE; + gfMapInventoryFilterPopupVisible = FALSE; + } + + POPUP_OPTION *pOption; + UINT32 uiFlags; + + // create a popup + gMapInventoryFilterPopup = new POPUP("MAP INVENTORY FILTER MENU POPUP"); // at this point the name is used mainly for debug output + + // add a callback that lets the keyboard handler know we're done (and ready to pop up again) + gMapInventoryFilterPopup->setCallback(POPUP_CALLBACK_HIDE, new popupCallbackFunction( &MapInventoryFilterMenuPopup_Hide ) ); + + CHAR16 pStr[300]; + + // Create menu to toggle groups of item classes on and off individually. + + swprintf( pStr, gzMapInventoryFilterOptions[ 0 ] ); + // Add option: "SHOW ALL" + uiFlags = IC_MAPFILTER_ALL; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterSet, uiFlags ) ); + if (guiMapInventoryFilter == IC_MAPFILTER_ALL) + { + // Set this option off. + pOption->setAvail(new popupCallbackFunction( &MapInventoryFilterMenuPopup_OptionOff, NULL )); + } + // Add the option to the menu. + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_GUN) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 1 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 1 ] ); + } + uiFlags = IC_MAPFILTER_GUN; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_AMMO) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 2 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 2 ] ); + } + uiFlags = IC_MAPFILTER_AMMO; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_EXPLOSV) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 3 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 3 ] ); + } + uiFlags = IC_MAPFILTER_EXPLOSV; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_MELEE) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 4 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 4 ] ); + } + uiFlags = IC_MAPFILTER_MELEE; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_ARMOR) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 5 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 5 ] ); + } + uiFlags = IC_MAPFILTER_ARMOR; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_LBE) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 6 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 6 ] ); + } + uiFlags = IC_MAPFILTER_LBE; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_KIT) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 7 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 7 ] ); + } + uiFlags = IC_MAPFILTER_KIT; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + if (guiMapInventoryFilter & IC_MAPFILTER_MISC) + { + // Hide guns + swprintf( pStr, L"%s *", gzMapInventoryFilterOptions[ 8 ] ); + } + else + { + // Show guns + swprintf( pStr, gzMapInventoryFilterOptions[ 8 ] ); + } + uiFlags = IC_MAPFILTER_MISC; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterToggle, uiFlags ) ); + gMapInventoryFilterPopup->addOption( *pOption ); + + swprintf( pStr, gzMapInventoryFilterOptions[ 9 ] ); + // Add option: "HIDE ALL" + uiFlags = 0; + pOption = new POPUP_OPTION(&std::wstring( pStr ), new popupCallbackFunction( &MapInventoryFilterMenuPopup_FilterSet, uiFlags ) ); + if (guiMapInventoryFilter == 0) + { + // Set this option off. + pOption->setAvail(new popupCallbackFunction( &MapInventoryFilterMenuPopup_OptionOff, NULL )); + } + // Add the option to the menu. + gMapInventoryFilterPopup->addOption( *pOption ); + + + UINT16 usPosX; + UINT16 usPosY; + + usPosX = ButtonList[guiMapInvenFilterButton[ 0 ]]->XLoc; + usPosY = ButtonList[guiMapInvenFilterButton[ 0 ]]->Area.RegionBottomRightY; + + gMapInventoryFilterPopup->setPosition( usPosX, usPosY ); + + gfMapInventoryFilterPopupInitialized = TRUE; + gfMapInventoryFilterPopupVisible = TRUE; + gfQueueRecreateMapInventoryFilterMenu = FALSE; + gMapInventoryFilterPopup->show(); +} + +// HEADROCK HAM 5: This function alters the Map Inventory Filter, by applying the argument as a flag toggle +// (XOR) to the current filter set. +// As a result of the alteration, some items will disappear from view - though they are still kept in +// memory as "Unseen" items. +void MapInventoryFilterMenuPopup_FilterToggle( UINT32 uiFlags ) +{ + // Hide the Filter Menu pop-up. + if (gMapInventoryFilterPopup != NULL && gfMapInventoryFilterPopupInitialized == TRUE) + { + gMapInventoryFilterPopup->hide(); + } + + // Alter the filter based on the flags we want. This is a XOR operation, so we're basically + // toggling the correct bits in the filter on-and-off. The bits correspond to the various + // item classes - though they are often toggled in groups (i.e. Guns + Launchers, Kits + Medkits + Camokits, etc.) + guiMapInventoryFilter ^= uiFlags; + // The refresh function moves the necessary items from the Seen to the Unseen inventories, and vice versa. + RefreshSeenAndUnseenPools(); + + BlitInventoryPoolGraphic( ); + + // Toggling these flags back on serves to cause the menu to stay open - yet refresh its options. As a result, + // we can keep clicking on the various options to see the items appear/disappear, and the options change to indicate + // this as well. + gfMapInventoryFilterPopupVisible = TRUE; + gfQueueRecreateMapInventoryFilterMenu = TRUE; +} + +// HEADROCK HAM 5: This function alters the Map Inventory Filter, by applying the argument as a flag set +// (=) to the current filter set. +// As a result of the alteration, some items will disappear from view - though they are still kept in +// memory as "Unseen" items. +void MapInventoryFilterMenuPopup_FilterSet( UINT32 uiFlags ) +{ + // Hide the Filter Menu pop-up. + if (gMapInventoryFilterPopup != NULL && gfMapInventoryFilterPopupInitialized == TRUE) + { + gMapInventoryFilterPopup->hide(); + } + + // Alter the filter based on the flags we want. This is a XOR operation, so we're basically + // toggling the correct bits in the filter on-and-off. The bits correspond to the various + // item classes - though they are often toggled in groups (i.e. Guns + Launchers, Kits + Medkits + Camokits, etc.) + guiMapInventoryFilter = uiFlags; + // The refresh function moves the necessary items from the Seen to the Unseen inventories, and vice versa. + RefreshSeenAndUnseenPools(); + + BlitInventoryPoolGraphic( ); + + // Toggling these flags back on serves to cause the menu to stay open - yet refresh its options. As a result, + // we can keep clicking on the various options to see the items appear/disappear, and the options change to indicate + // this as well. + gfMapInventoryFilterPopupVisible = TRUE; + gfQueueRecreateMapInventoryFilterMenu = TRUE; +} + +void MapInventoryFilterMenuPopup_Hide( void ) +{ + // When the pop-up goes away, the button that spawned it is popped back to the OFF position. + ButtonList[ guiMapInvenFilterButton[ 0 ] ]->uiFlags &=~ (BUTTON_CLICKED_ON); + + // Signal the renderer to stop drawing this menu. + gfMapInventoryFilterPopupVisible = FALSE; + // Dirty! + fMapPanelDirty = TRUE; +} + +BOOLEAN MapInventoryFilterMenuPopup_OptionOff( void ) +{ + // Turns an option off by always returning FALSE + return FALSE; +} + +// HEADROCK HAM 5: Alternative functions that alter the inventory filter without the need for a popup. +void MapInventoryFilterToggle( UINT32 uiFlags ) +{ + // Alter the filter based on the flags we want. This is a XOR operation, so we're basically + // toggling the correct bits in the filter on-and-off. The bits correspond to the various + // item classes - though they are often toggled in groups (i.e. Guns + Launchers, Kits + Medkits + Camokits, etc.) + guiMapInventoryFilter ^= uiFlags; + // The refresh function moves the necessary items from the Seen to the Unseen inventories, and vice versa. + RefreshSeenAndUnseenPools(); + + BlitInventoryPoolGraphic( ); + + HandleSetFilterButtons(); +} + +void MapInventoryFilterSet( UINT32 uiFlags ) +{ + // Alter the filter based on the flags we want. This is a XOR operation, so we're basically + // toggling the correct bits in the filter on-and-off. The bits correspond to the various + // item classes - though they are often toggled in groups (i.e. Guns + Launchers, Kits + Medkits + Camokits, etc.) + guiMapInventoryFilter = uiFlags; + // The refresh function moves the necessary items from the Seen to the Unseen inventories, and vice versa. + RefreshSeenAndUnseenPools(); + + BlitInventoryPoolGraphic( ); + + HandleSetFilterButtons(); +} + +// HEADROCK HAM 5: Handle toggling the buttons on and off depending on which filters are shown. +void HandleSetFilterButtons() +{ + // Show/Hide All button always off. + ButtonList[guiMapInvenFilterButton[ 0 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + + // Guns + if (guiMapInventoryFilter & IC_MAPFILTER_GUN) + { + ButtonList[guiMapInvenFilterButton[ 1 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 1 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Ammo + if (guiMapInventoryFilter & IC_MAPFILTER_AMMO) + { + ButtonList[guiMapInvenFilterButton[ 2 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 2 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Explosives + if (guiMapInventoryFilter & IC_MAPFILTER_EXPLOSV) + { + ButtonList[guiMapInvenFilterButton[ 3 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 3 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Melee Weapons + if (guiMapInventoryFilter & IC_MAPFILTER_MELEE) + { + ButtonList[guiMapInvenFilterButton[ 4 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 4 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Armor + if (guiMapInventoryFilter & IC_MAPFILTER_ARMOR) + { + ButtonList[guiMapInvenFilterButton[ 5 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 5 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // LBEs + if (guiMapInventoryFilter & IC_MAPFILTER_LBE) + { + ButtonList[guiMapInvenFilterButton[ 6 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 6 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Kits + if (guiMapInventoryFilter & IC_MAPFILTER_KIT) + { + ButtonList[guiMapInvenFilterButton[ 7 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 7 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } + + // Misc. Items + if (guiMapInventoryFilter & IC_MAPFILTER_MISC) + { + ButtonList[guiMapInvenFilterButton[ 8 ]]->uiFlags |= (BUTTON_CLICKED_ON); + } + else + { + ButtonList[guiMapInvenFilterButton[ 8 ]]->uiFlags &=~ (BUTTON_CLICKED_ON); + } +} \ No newline at end of file diff --git a/Strategic/Map Screen Interface Map Inventory.h b/Strategic/Map Screen Interface Map Inventory.h index 34644e77..f24265db 100644 --- a/Strategic/Map Screen Interface Map Inventory.h +++ b/Strategic/Map Screen Interface Map Inventory.h @@ -12,6 +12,9 @@ // whether we are showing the inventory pool graphic extern BOOLEAN fShowMapInventoryPool; +// HEADROCK HAM 5: Flag telling us whether we've already redrawn the screen to show +// sector inventory sale prices. +extern UINT8 gubRenderedMapInventorySalePrices; // load inventory pool graphic BOOLEAN LoadInventoryPoolGraphic( void ); @@ -21,6 +24,8 @@ void RemoveInventoryPoolGraphic( void ); // blit the inventory graphic void BlitInventoryPoolGraphic( void ); +// HEADROCK HAM 5: Blit map inventory slots +void BlitInventoryPoolSlotGraphics( void ); // which buttons in map invneotyr panel? void HandleButtonStatesWhileMapInventoryActive( void ); @@ -52,6 +57,8 @@ extern INT32 sObjectSourceGridNo; extern INT32 iCurrentInventoryPoolPage; extern INT32 iLastInventoryPoolPage; extern BOOLEAN fMapInventoryItemCompatable[ ]; +// HEADROCK HAM 5: Same idea as above, this flags items as being candidates for appearing in Zoomed mode. +extern BOOLEAN gfMapInventoryItemToZoom[ ]; extern INT32 MAP_INVENTORY_POOL_SLOT_COUNT; BOOLEAN IsMapScreenWorldItemInvisibleInMapInventory( WORLDITEM *pWorldItem ); diff --git a/Strategic/Map Screen Interface Map.cpp b/Strategic/Map Screen Interface Map.cpp index 16c105e9..823e0910 100644 --- a/Strategic/Map Screen Interface Map.cpp +++ b/Strategic/Map Screen Interface Map.cpp @@ -221,7 +221,7 @@ INT32 iZoomY = 0; #define ZOOM_SOUTH_ARROW 77 #define ZOOM_EAST_ARROW 78 #define ZOOM_WEST_ARROW 79 -#define ARROW_DELAY 20 +#define ARROW_DELAY 70 // HEADROCK HAM 5: Slowed down as part of new arrow system #define PAUSE_DELAY 1000 // The zoomed in path lines @@ -269,7 +269,8 @@ INT32 iZoomY = 0; #define RED_SOUTH_OFF_Y 21 // the font use on the mvt icons for mapscreen -#define MAP_MVT_ICON_FONT SMALLCOMPFONT +// HEADROCK HAM 5: Externalized for larger map fonts. +//#define MAP_MVT_ICON_FONT SMALLCOMPFONT // map shading colors @@ -334,8 +335,10 @@ UINT16 MAP_MILITIA_BOX_POS_Y; #define HELI_ICON 0 #define HELI_SHADOW_ICON 1 -#define HELI_ICON_WIDTH 20 -#define HELI_ICON_HEIGHT 10 +// HEADROCK HAM 5: Now dynamically read from helicopter icon file. +//#define HELI_ICON_WIDTH 20 +//#define HELI_ICON_HEIGHT 10 +// HEADROCK HAM 5: No longer drawn. #define HELI_SHADOW_ICON_WIDTH 19 #define HELI_SHADOW_ICON_HEIGHT 11 @@ -368,7 +371,8 @@ UINT32 guiSubLevel1, guiSubLevel2, guiSubLevel3; // the between sector icons UINT32 guiCHARBETWEENSECTORICONS; -UINT32 guiCHARBETWEENSECTORICONSCLOSE; +// HEADROCK HAM 5.1: Enemies between sectors icons +UINT32 guiENEMYBETWEENSECTORICONS; extern BOOLEAN fMapScreenBottomDirty; @@ -480,6 +484,9 @@ BOOLEAN fDrawTempHeliPath = FALSE; // the map arrows graphics UINT32 guiMAPCURSORS; +// HEADROCK HAM 5: New pathing arrows may replace the above eventually, but for now a separate variable will do. +UINT32 guiMapPathingArrows; + // destination plotting character INT8 bSelectedDestChar = -1; @@ -532,7 +539,7 @@ void DrawTownLabels(STR16 pString, STR16 pStringA,UINT16 usFirstX, UINT16 usFirs void ShowTeamAndVehicles(INT32 fShowFlags); BOOLEAN ShadeMapElem( INT16 sMapX, INT16 sMapY, INT32 iColor ); BOOLEAN ShadeMapElemZoomIn(INT16 sMapX, INT16 sMapY, INT32 iColor ); -void AdjustXForLeftMapEdge(STR16 wString, INT16 *psX); +void AdjustXForLeftMapEdge(STR16 wString, INT16 *psX, INT32 iFont); void BlitTownGridMarkers( void ); void BlitMineGridMarkers( void ); void BlitSAMGridMarkers( void ); @@ -1208,6 +1215,17 @@ void DrawTownLabels(STR16 pString, STR16 pStringA, UINT16 usFirstX, UINT16 usFir INT16 sSecondX, sSecondY; INT16 sPastEdge; + // HEADROCK HAM 5: Now variable... + INT32 MapTownLabelsFont; + if (iResolution <= _800x600 ) + { + MapTownLabelsFont = MAP_FONT; + } + else + { + MapTownLabelsFont = FONT14ARIAL; + } + SetFont( MapTownLabelsFont ); // if within view region...render, else don't if( ( usFirstX > MAP_VIEW_START_X + MAP_VIEW_WIDTH )||( usFirstX < MAP_VIEW_START_X )|| (usFirstY < MAP_VIEW_START_Y ) || ( usFirstY > MAP_VIEW_START_Y + MAP_VIEW_HEIGHT ) ) @@ -1222,7 +1240,7 @@ void DrawTownLabels(STR16 pString, STR16 pStringA, UINT16 usFirstX, UINT16 usFir ClipBlitsToMapViewRegion( ); // we're CENTERING the first string AROUND usFirstX, so calculate the starting X - usFirstX -= StringPixLength( pString, MAP_FONT) / 2; + usFirstX -= StringPixLength( pString, MapTownLabelsFont) / 2; // print first string gprintfdirty( usFirstX, usFirstY, pString); @@ -1230,7 +1248,7 @@ void DrawTownLabels(STR16 pString, STR16 pStringA, UINT16 usFirstX, UINT16 usFir // calculate starting coordinates for the second string - VarFindFontCenterCoordinates(( INT16 )( usFirstX ), ( INT16 )usFirstY, StringPixLength( pString, MAP_FONT), 0, MAP_FONT, &sSecondX, &sSecondY, pStringA); + VarFindFontCenterCoordinates(( INT16 )( usFirstX ), ( INT16 )usFirstY, StringPixLength( pString, MapTownLabelsFont), 0, MapTownLabelsFont, &sSecondX, &sSecondY, pStringA); // make sure we don't go past left edge (Grumm) if( !fZoomFlag ) @@ -1242,7 +1260,7 @@ void DrawTownLabels(STR16 pString, STR16 pStringA, UINT16 usFirstX, UINT16 usFir } // print second string beneath first - sSecondY = ( INT16 )( usFirstY + GetFontHeight( MAP_FONT ) ); + sSecondY = ( INT16 )( usFirstY + GetFontHeight( MapTownLabelsFont ) ); gprintfdirty( sSecondX, sSecondY, pStringA ); mprintf( sSecondX, sSecondY, pStringA ); @@ -3394,838 +3412,241 @@ void RestoreArrowBackgroundsForTrace(INT32 iArrow, INT32 iArrowX, INT32 iArrowY, BOOLEAN TraceCharAnimatedRoute( PathStPtr pPath, BOOLEAN fCheckFlag, BOOLEAN fForceUpDate ) { - static PathStPtr pCurrentNode=NULL; - static INT8 bCurrentChar=-1; - static BOOLEAN fUpDateFlag=FALSE; - static BOOLEAN fPauseFlag=TRUE; - static UINT8 ubCounter=1; + static PathStPtr pCurrentNode=NULL; + static BOOLEAN fPauseFlag=TRUE; - HVOBJECT hMapHandle; - BOOLEAN fSpeedFlag=FALSE; - INT32 iDifference=0; - INT32 iArrow=-1; - INT32 iX = 0, iY = 0; - INT32 iPastX, iPastY; - INT16 sX = 0, sY = 0; - INT32 iArrowX, iArrowY; - INT32 iDeltaA, iDeltaB, iDeltaB1; - INT32 iDirection = -1; - BOOLEAN fUTurnFlag=FALSE; - BOOLEAN fNextNode=FALSE; - PathStPtr pTempNode=NULL; - PathStPtr pNode=NULL; - PathStPtr pPastNode=NULL; - PathStPtr pNextNode=NULL; + HVOBJECT hMapHandle; + INT32 iDifference=0; + INT32 iArrow=-1; + INT32 iX = 0, iY = 0; + INT32 iArrowX, iArrowY; + INT32 iDeltaNext, iDeltaPrev; + INT32 iDirectionArriving, iDirectionExiting; + UINT32 uiArrowNumToDraw; + UINT16 usArrowWidth; + UINT16 usArrowHeight; + PathStPtr pTempNode=NULL; + PathStPtr pNode=NULL; + PathStPtr pPastNode=NULL; + PathStPtr pNextNode=NULL; // must be plotting movement - if ( ( bSelectedDestChar == -1 ) && ( fPlotForHelicopter == FALSE ) ) - { - return FALSE; - } - - // if any nodes have been deleted, reset current node to beginning of the list - if( fDeletedNode ) - { - fDeletedNode = FALSE; - pCurrentNode = NULL; - } - - - // Valid path? - if ( pPath == NULL ) - { - return FALSE; - } - else - { - if(pCurrentNode==NULL) + if ( ( bSelectedDestChar == -1 ) && ( fPlotForHelicopter == FALSE ) ) { - pCurrentNode = pPath; + return FALSE; } - } - // Check Timer - if (giAnimateRouteBaseTime==0) - { - giAnimateRouteBaseTime=GetJA2Clock(); - return FALSE; - } + // if any nodes have been deleted, reset current node to beginning of the list + if( fDeletedNode ) + { + fDeletedNode = FALSE; + pCurrentNode = NULL; + } - // check difference in time - iDifference=GetJA2Clock()-giAnimateRouteBaseTime; - - // if pause flag, check time, if time passed, reset, continue on, else return - if(fPauseFlag) - { - if(iDifference < PAUSE_DELAY) + // Valid path? + if ( pPath == NULL ) { return FALSE; } else { - fPauseFlag=FALSE; - giAnimateRouteBaseTime=GetJA2Clock(); + if(pCurrentNode == NULL) + { + pCurrentNode = pPath; + } } - } + // Check Timer + if (giAnimateRouteBaseTime==0) + { + giAnimateRouteBaseTime=GetJA2Clock(); + return FALSE; + } - // if is checkflag and change in status, return TRUE; - if(!fForceUpDate) - { - if(iDifference < ARROW_DELAY) - { - if (!fUpDateFlag) - return FALSE; - } - else - { - // sufficient time, update base time - giAnimateRouteBaseTime=GetJA2Clock(); - fUpDateFlag=!fUpDateFlag; + // check difference in time + iDifference=GetJA2Clock()-giAnimateRouteBaseTime; - if(fCheckFlag) - return TRUE; + // if pause flag, check time, if time passed, reset, continue on, else return + if(fPauseFlag) + { + if(iDifference < PAUSE_DELAY) + { + return FALSE; + } + else + { + fPauseFlag=FALSE; + giAnimateRouteBaseTime=GetJA2Clock(); + } + } - fNextNode=TRUE; - } - } + // If we aren't being forced to update the arrows... + if(!fForceUpDate) + { + // If we're ready to draw the next arrow, + if(iDifference >= ARROW_DELAY) + { + // sufficient time has passed, update base time + giAnimateRouteBaseTime=GetJA2Clock(); + pCurrentNode = pCurrentNode->pNext; + + if (pCurrentNode == NULL) + { + fPauseFlag = TRUE; + return FALSE; + } + + // Are we just checking? + if(fCheckFlag) + { + // Then return true to signal that we're ready to draw the next arrow. + return TRUE; + } + } + } // check to see if Current node has not been deleted + + // Clone the first node in the path pTempNode = pPath; - while(pTempNode) - { - if(pTempNode==pCurrentNode) + // Scan through this cloned path + while(pTempNode) + { + // Have we found our node? + if(pTempNode==pCurrentNode) { - //not deleted - //reset pause flag - break; + //Good, it hasn't been deleted + break; + } + else + { + // Continue scanning + pTempNode=pTempNode->pNext; } - else - pTempNode=pTempNode->pNext; } - // if deleted, restart at beginnning - if(pTempNode==NULL) - { - pCurrentNode = pPath; + // if deleted, restart at beginning + if(pTempNode==NULL) + { + pCurrentNode = pPath; // First node in the path - // set pause flag - if(!pCurrentNode) - return FALSE; - - } - - // Grab Video Objects - GetVideoObject(&hMapHandle, guiMAPCURSORS); - - // Handle drawing of arrow - pNode=pCurrentNode; - if((!pNode->pPrev)&&(ubCounter==1)&&(fForceUpDate)) - { - ubCounter=0; - return FALSE; - } - else if((ubCounter==1)&&(fForceUpDate)) - { - pNode=pCurrentNode->pPrev; - } - if (pNode->pNext) - pNextNode=pNode->pNext; - else - pNextNode=NULL; - - if (pNode->pPrev) - pPastNode=pNode->pPrev; - else - pPastNode=NULL; - - // go through characters list and display arrows for path - fUTurnFlag=FALSE; - if ((pPastNode)&&(pNextNode)) + // No path? + if(!pCurrentNode) { - iDeltaA=(INT16)pNode->uiSectorId-(INT16)pPastNode->uiSectorId; - iDeltaB=(INT16)pNode->uiSectorId-(INT16)pNextNode->uiSectorId; - if(!pNode->fSpeed) - fSpeedFlag=TRUE; - else - fSpeedFlag=FALSE; - if (iDeltaA ==0) - return FALSE; - if(!fZoomFlag) - { - iX=(pNode->uiSectorId%MAP_WORLD_X); - iY=(pNode->uiSectorId/MAP_WORLD_X); - iX=(iX*MAP_GRID_X)+MAP_VIEW_START_X; - iY=(iY*MAP_GRID_Y)+MAP_VIEW_START_Y; - } - else - { - GetScreenXYFromMapXYStationary( ((UINT16)(pNode->uiSectorId%MAP_WORLD_X)),((UINT16)(pNode->uiSectorId/MAP_WORLD_X)) , &sX, &sY ); - iY=sY-MAP_GRID_Y; - iX=sX-MAP_GRID_X; - } - iArrowX=iX; - iArrowY=iY; - if ((pPastNode->pPrev)&&(pNextNode->pNext)) - { - fUTurnFlag=FALSE; - // check to see if out-of sector U-turn - // for placement of arrows - iDeltaB1=pNextNode->uiSectorId-pNextNode->pNext->uiSectorId; - if ((iDeltaB1==-WORLD_MAP_X)&&(iDeltaA==-WORLD_MAP_X)&&(iDeltaB==-1)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==-WORLD_MAP_X)&&(iDeltaA==-WORLD_MAP_X)&&(iDeltaB==1)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==WORLD_MAP_X)&&(iDeltaA==WORLD_MAP_X)&&(iDeltaB==1)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==-WORLD_MAP_X)&&(iDeltaA==-WORLD_MAP_X)&&(iDeltaB==1)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==-1)&&(iDeltaA==-1)&&(iDeltaB==-WORLD_MAP_X)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==-1)&&(iDeltaA==-1)&&(iDeltaB==WORLD_MAP_X)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==1)&&(iDeltaA==1)&&(iDeltaB==-WORLD_MAP_X)) - { - fUTurnFlag=TRUE; - } - else if((iDeltaB1==1)&&(iDeltaA==1)&&(iDeltaB==WORLD_MAP_X)) - { - fUTurnFlag=TRUE; - } - else - fUTurnFlag=FALSE; - } - - - if ((pPastNode->uiSectorId==pNextNode->uiSectorId)) - { - if (pPastNode->uiSectorId+WORLD_MAP_X==pNode->uiSectorId) - { - if(fZoomFlag) - { - iDirection=S_TO_N_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_NORTH_ARROW; - else if(fSpeedFlag) - iArrow=ZOOM_Y_NORTH_ARROW; - else - iArrow=ZOOM_NORTH_ARROW; - iArrowX+=NORTH_OFFSET_X*2; - iArrowY+=NORTH_OFFSET_Y*2; - } - else - { - iDirection=S_TO_N_LINE; - if(!ubCounter) - iArrow=W_NORTH_ARROW; - else if(fSpeedFlag) - iArrow=Y_NORTH_ARROW; - else - iArrow=NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X; - iArrowY+=NORTH_OFFSET_Y; - } - } - else if(pPastNode->uiSectorId-WORLD_MAP_X==pNode->uiSectorId) - { - if(fZoomFlag) - { - iDirection=N_TO_S_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_SOUTH_ARROW; - else if(fSpeedFlag) - iArrow=ZOOM_Y_SOUTH_ARROW; - else - iArrow=ZOOM_SOUTH_ARROW; - iArrowX+=SOUTH_OFFSET_X*2; - iArrowY+=SOUTH_OFFSET_Y*2; - } - else - { - iDirection=N_TO_S_LINE; - if(!ubCounter) - iArrow=W_SOUTH_ARROW; - else if(fSpeedFlag) - iArrow=Y_SOUTH_ARROW; - else - iArrow=SOUTH_ARROW; - iArrowX+=SOUTH_OFFSET_X; - iArrowY+=SOUTH_OFFSET_Y; - } - } - else if (pPastNode->uiSectorId+1==pNode->uiSectorId) - { - if(fZoomFlag) - { - iDirection=E_TO_W_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_WEST_ARROW; - else - iArrow=ZOOM_WEST_ARROW; - iArrowX+=WEST_OFFSET_X*2; - iArrowY+=WEST_OFFSET_Y*2; - } - else - { - iDirection=E_TO_W_LINE; - if(!ubCounter) - iArrow=W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=Y_WEST_ARROW; - else - iArrow=WEST_ARROW; - iArrowX+=WEST_OFFSET_X; - iArrowY+=WEST_OFFSET_Y; - } - } - else - { - if(fZoomFlag) - { - iDirection=W_TO_E_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_EAST_ARROW; - else - iArrow=ZOOM_EAST_ARROW; - iArrowX+=EAST_OFFSET_X*2; - iArrowY+=EAST_OFFSET_Y*2; - } - else - { - iDirection=W_TO_E_LINE; - if(!ubCounter) - iArrow=W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=Y_EAST_ARROW; - else - iArrow=EAST_ARROW; - iArrowX+=EAST_OFFSET_X; - iArrowY+=EAST_OFFSET_Y; - } - } - } - else - { - if ((iDeltaA==-1)&&(iDeltaB==1)) - { - if(fZoomFlag) - { - iDirection=WEST_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_WEST_ARROW; - else - iArrow=ZOOM_WEST_ARROW; - - iArrowX+=WEST_OFFSET_X*2; - iArrowY+=WEST_OFFSET_Y*2; - } - else - { - iDirection=WEST_LINE; - if(!ubCounter) - iArrow=W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=Y_WEST_ARROW; - else - iArrow=WEST_ARROW; - - - iArrowX+=WEST_OFFSET_X; - iArrowY+=WEST_OFFSET_Y; - } - } - else if((iDeltaA==1)&&(iDeltaB==-1)) - { - if(fZoomFlag) - { - iDirection=EAST_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_EAST_ARROW; - else - iArrow=ZOOM_EAST_ARROW; - - iArrowX+=EAST_OFFSET_X*2; - iArrowY+=EAST_OFFSET_Y*2; - } - else - { - iDirection=EAST_LINE; - if(!ubCounter) - iArrow=W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=Y_EAST_ARROW; - else - iArrow=EAST_ARROW; - - iArrowX+=EAST_OFFSET_X; - iArrowY+=EAST_OFFSET_Y; - } - } - else if((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=NORTH_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_NORTH_ARROW; - else - iArrow=ZOOM_NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X*2; - iArrowY+=NORTH_OFFSET_Y*2; - } - else - { - iDirection=NORTH_LINE; - if(!ubCounter) - iArrow=W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_NORTH_ARROW; - else - iArrow=NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X; - iArrowY+=NORTH_OFFSET_Y; - } - } - else if((iDeltaA==WORLD_MAP_X)&&(iDeltaB==-WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=SOUTH_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_SOUTH_ARROW; - else - iArrow=ZOOM_SOUTH_ARROW; - - iArrowX+=SOUTH_OFFSET_X*2; - iArrowY+=SOUTH_OFFSET_Y*2; - } - else - { - iDirection=SOUTH_LINE; - if(!ubCounter) - iArrow=W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_SOUTH_ARROW; - else - iArrow=SOUTH_ARROW; - - iArrowX+=SOUTH_OFFSET_X; - iArrowY+=SOUTH_OFFSET_Y; - } - } - else if((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==-1)) - { - if(fZoomFlag) - { - iDirection=N_TO_E_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_EAST_ARROW; - else - iArrow=ZOOM_EAST_ARROW; - - iArrowX+=EAST_OFFSET_X*2; - iArrowY+=EAST_OFFSET_Y*2; - } - else - { - iDirection=N_TO_E_LINE; - if(!ubCounter) - iArrow=W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=Y_EAST_ARROW; - else - iArrow=EAST_ARROW; - - iArrowX+=EAST_OFFSET_X; - iArrowY+=EAST_OFFSET_Y; - } - } - else if((iDeltaA==WORLD_MAP_X)&&(iDeltaB==1)) - { - if(fZoomFlag) - { - iDirection=S_TO_W_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_WEST_ARROW; - else - iArrow=ZOOM_WEST_ARROW; - - iArrowX+=WEST_OFFSET_X*2; - iArrowY+=WEST_OFFSET_Y*2; - } - else - { - iDirection=S_TO_W_LINE; - if(!ubCounter) - iArrow=W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=Y_WEST_ARROW; - else - iArrow=WEST_ARROW; - - - iArrowX+=WEST_OFFSET_X; - iArrowY+=WEST_OFFSET_Y; - } - } - else if((iDeltaA==1)&&(iDeltaB==-WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=E_TO_S_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_SOUTH_ARROW; - else - iArrow=ZOOM_SOUTH_ARROW; - - iArrowX+=SOUTH_OFFSET_X*2; - iArrowY+=SOUTH_OFFSET_Y*2; - } - else - { - iDirection=E_TO_S_LINE; - if(!ubCounter) - iArrow=W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_SOUTH_ARROW; - else - iArrow=SOUTH_ARROW; - - iArrowX+=SOUTH_OFFSET_X; - iArrowY+=SOUTH_OFFSET_Y; - } - } - else if ((iDeltaA==-1)&&(iDeltaB==WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=W_TO_N_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_NORTH_ARROW; - else - iArrow=ZOOM_NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X*2; - iArrowY+=NORTH_OFFSET_Y*2; - } - else - { - iDirection=W_TO_N_LINE; - if(!ubCounter) - iArrow=W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_NORTH_ARROW; - else - iArrow=NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X; - iArrowY+=NORTH_OFFSET_Y; - } - } - else if ((iDeltaA==-1)&&(iDeltaB==-WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=W_TO_S_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_SOUTH_ARROW; - else - iArrow=ZOOM_SOUTH_ARROW; - - iArrowX+=SOUTH_OFFSET_X*2; - iArrowY+=(SOUTH_OFFSET_Y+WEST_TO_SOUTH_OFFSET_Y)*2; - } - else - { - iDirection=W_TO_S_LINE; - if(!ubCounter) - iArrow=W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_SOUTH_ARROW; - else - iArrow=SOUTH_ARROW; - iArrowX+=SOUTH_OFFSET_X; - iArrowY+=(SOUTH_OFFSET_Y+WEST_TO_SOUTH_OFFSET_Y); - } - } - else if ((iDeltaA==-WORLD_MAP_X)&&(iDeltaB==1)) - { - if(fZoomFlag) - { - iDirection=N_TO_W_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_WEST_ARROW; - else - iArrow=ZOOM_WEST_ARROW; - - iArrowX+=WEST_OFFSET_X*2; - iArrowY+=WEST_OFFSET_Y*2; - } - else - { - iDirection=N_TO_W_LINE; - if(!ubCounter) - iArrow=W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=Y_WEST_ARROW; - else - iArrow=WEST_ARROW; - - iArrowX+=WEST_OFFSET_X; - iArrowY+=WEST_OFFSET_Y; - } - } - else if ((iDeltaA==WORLD_MAP_X)&&(iDeltaB==-1)) - { - if(fZoomFlag) - { - iDirection=S_TO_E_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_EAST_ARROW; - else - iArrow=ZOOM_EAST_ARROW; - iArrowX+=EAST_OFFSET_X*2; - iArrowY+=EAST_OFFSET_Y*2; - } - else - { - iDirection=S_TO_E_LINE; - if(!ubCounter) - iArrow=W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=Y_EAST_ARROW; - else - iArrow=EAST_ARROW; - iArrowX+=EAST_OFFSET_X; - iArrowY+=EAST_OFFSET_Y; - } - } - else if ((iDeltaA==1)&&(iDeltaB==WORLD_MAP_X)) - { - if(fZoomFlag) - { - iDirection=E_TO_N_ZOOM_LINE; - if(!ubCounter) - iArrow=ZOOM_W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=ZOOM_Y_NORTH_ARROW; - else - iArrow=ZOOM_NORTH_ARROW; - iArrowX+=(NORTH_OFFSET_X*2); - iArrowY+=(NORTH_OFFSET_Y+EAST_TO_NORTH_OFFSET_Y)*2; - } - else - { - iDirection=E_TO_N_LINE; - if(!ubCounter) - iArrow=W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_NORTH_ARROW; - else - iArrow=NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X; - iArrowY+=NORTH_OFFSET_Y+EAST_TO_NORTH_OFFSET_Y; - } - } - } - + // FALSE! + return FALSE; } + } - else + // Get the X/Y of the top left corner of this sector. + iX = (pCurrentNode->uiSectorId % MAP_WORLD_X); + iY = (pCurrentNode->uiSectorId / MAP_WORLD_X); + + iX = (iX*MAP_GRID_X) + MAP_VIEW_START_X; + iY = (iY*MAP_GRID_Y) + MAP_VIEW_START_Y; + + if (iResolution < _800x600) + { + iArrowX = iX - 4; + iArrowY = iY - 4; + } + else if (iResolution < _1024x768) + { + iArrowX = iX - 5; + iArrowY = iY - 5; + } + else + { + iArrowX = iX - 6; + iArrowY = iY - 6; + } + + // Find the next node + if (pCurrentNode->pNext) + { + pNextNode=pCurrentNode->pNext; + iDeltaNext=(INT16)pNextNode->uiSectorId - (INT16)pCurrentNode->uiSectorId; + } + else + { + pNextNode=NULL; + iDeltaNext = 0; + } + + // Find the previous node. + if (pCurrentNode->pPrev) + { + pPastNode=pCurrentNode->pPrev; + iDeltaPrev=(INT16)pPastNode->uiSectorId - (INT16)pCurrentNode->uiSectorId; + } + else + { + pPastNode=NULL; + iDeltaPrev = 0; + } + + // Figure out direction of movement + switch (iDeltaPrev) + { + case -MAP_WORLD_Y: + iDirectionArriving = 1; + break; + case 1: + iDirectionArriving = 2; + break; + case MAP_WORLD_Y: + iDirectionArriving = 3; + break; + case -1: + iDirectionArriving = 4; + break; + default: + iDirectionArriving = 0; + break; + } + + switch (iDeltaNext) + { + case -MAP_WORLD_Y: + iDirectionExiting = 0; + break; + case 1: + iDirectionExiting = 1; + break; + case MAP_WORLD_Y: + iDirectionExiting = 2; + break; + case -1: + iDirectionExiting = 3; + break; + default: + return (FALSE); + } + + if (iDirectionArriving == 0) + { + if (pPastNode) { - iX=(pNode->uiSectorId%MAP_WORLD_X); - iY=(pNode->uiSectorId/MAP_WORLD_X); - iX=(iX*MAP_GRID_X)+MAP_VIEW_START_X; - iY=(iY*MAP_GRID_Y)+MAP_VIEW_START_Y; - if(pPastNode) + if (pPastNode->pPrev) { - iPastX=(pPastNode->uiSectorId%MAP_WORLD_X); - iPastY=(pPastNode->uiSectorId/MAP_WORLD_X); - iPastX=(iPastX*MAP_GRID_X)+MAP_VIEW_START_X; - iPastY=(iPastY*MAP_GRID_Y)+MAP_VIEW_START_Y; + INT8 A = 1; } - if(pNode->fSpeed) - fSpeedFlag=TRUE; - else - fSpeedFlag=FALSE; - iArrowX=iX; - iArrowY=iY; - // display enter and exit 'X's - if (pPastNode) - { - // red 'X' - fUTurnFlag=TRUE; - iDeltaA=(INT16)pNode->uiSectorId-(INT16)pPastNode->uiSectorId; - if (iDeltaA==-1) - { - iDirection=RED_X_WEST; - //iX+=RED_WEST_OFF_X; - } - else if (iDeltaA==1) - { - iDirection=RED_X_EAST; - //iX+=RED_EAST_OFF_X; - } - else if(iDeltaA==-WORLD_MAP_X) - { - iDirection=RED_X_NORTH; - //iY+=RED_NORTH_OFF_Y; - } - else - { - iDirection=RED_X_SOUTH; - // iY+=RED_SOUTH_OFF_Y; - } - } - if (pNextNode) - { - fUTurnFlag=FALSE; - iDeltaB=(INT16)pNode->uiSectorId-(INT16)pNextNode->uiSectorId; - if (iDeltaB==-1) - { - iDirection=GREEN_X_EAST; - if(!ubCounter) - iArrow=W_EAST_ARROW; - else if (fSpeedFlag) - iArrow=Y_EAST_ARROW; - else - iArrow=EAST_ARROW; - - iArrowX+=EAST_OFFSET_X; - iArrowY+=EAST_OFFSET_Y; - //iX+=RED_EAST_OFF_X; - } - else if (iDeltaB==1) - { - iDirection=GREEN_X_WEST; - if(!ubCounter) - iArrow=W_WEST_ARROW; - else if (fSpeedFlag) - iArrow=Y_WEST_ARROW; - else - iArrow=WEST_ARROW; - - iArrowX+=WEST_OFFSET_X; - iArrowY+=WEST_OFFSET_Y; - //iX+=RED_WEST_OFF_X; - } - else if(iDeltaB==WORLD_MAP_X) - { - iDirection=GREEN_X_NORTH; - if(!ubCounter) - iArrow=W_NORTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_NORTH_ARROW; - else - iArrow=NORTH_ARROW; - - iArrowX+=NORTH_OFFSET_X; - iArrowY+=NORTH_OFFSET_Y; - //iY+=RED_NORTH_OFF_Y; - } - else - { - iDirection=GREEN_X_SOUTH; - if(!ubCounter) - iArrow=W_SOUTH_ARROW; - else if (fSpeedFlag) - iArrow=Y_SOUTH_ARROW; - else - iArrow=SOUTH_ARROW; - iArrowX+=SOUTH_OFFSET_X; - iArrowY+=SOUTH_OFFSET_Y; - //iY+=RED_SOUTH_OFF_Y; - } - - } - } - if(fNextNode) - { - if(!ubCounter) - { - pCurrentNode=pCurrentNode->pNext; - if(!pCurrentNode) - fPauseFlag=TRUE; - } - } - if ((iDirection !=-1)&&(iArrow!=-1)) - { + } - if(!fUTurnFlag) - { - if((!fZoomFlag)||((fZoomFlag)&&(iX >MAP_VIEW_START_X)&&(iY >MAP_VIEW_START_Y)&&(iX < 640-MAP_GRID_X*2)&&(iY < MAP_VIEW_START_Y+MAP_VIEW_HEIGHT))) - { + uiArrowNumToDraw = 20 + (iDirectionArriving * 4) + iDirectionExiting; - //if(!fZoomFlag) - //RestoreExternBackgroundRect(((INT16)iArrowX),((INT16)iArrowY),DMAP_GRID_X, DMAP_GRID_Y); - //else - //RestoreExternBackgroundRect(((INT16)iArrowX), ((INT16)iArrowY),DMAP_GRID_ZOOM_X, DMAP_GRID_ZOOM_Y); - if( pNode != pPath ) - { - BltVideoObject(FRAME_BUFFER, hMapHandle, (UINT16)iArrow, iArrowX, iArrowY, VO_BLT_SRCTRANSPARENCY, NULL ); - InvalidateRegion( iArrowX, iArrowY, iArrowX + 2 * MAP_GRID_X, iArrowY + 2 * MAP_GRID_Y ); - } - } - if(ubCounter==1) - ubCounter=0; - else - ubCounter=1; - return TRUE; - } - if(ubCounter==1) - ubCounter=0; - else - ubCounter=1; + // Get the video object containing all movement arrows. + GetVideoObject(&hMapHandle, guiMapPathingArrows); + usArrowWidth = hMapHandle->pETRLEObject[ uiArrowNumToDraw ].usWidth; + usArrowHeight = hMapHandle->pETRLEObject[ uiArrowNumToDraw ].usHeight; + BltVideoObject(FRAME_BUFFER, hMapHandle, uiArrowNumToDraw, iArrowX, iArrowY, VO_BLT_SRCTRANSPARENCY, NULL ); + InvalidateRegion( iArrowX, iArrowY, iArrowX + usArrowWidth, iArrowY + usArrowHeight ); - } - // move to next arrow - - -//ARM who knows what it should return here? + //ARM who knows what it should return here? return FALSE; } @@ -4287,18 +3708,6 @@ void DisplayThePotentialPathForHelicopter(INT16 sMapX, INT16 sMapY ) BOOLEAN IsTheCursorAllowedToHighLightThisSector( INT16 sSectorX, INT16 sSectorY ) { - //Ja25UB - // check to see if this sector is a blocked out sector? -/* if ( !SectorInfo[ ( SECTOR( sSectorX, sSectorY ) ) ].fValidSector ) - { - return ( FALSE ); - } - else - { - // return cursor is allowed to highlight this sector - return ( TRUE ); - } -*/ // check to see if this sector is a blocked out sector? if( sBadSectorsList[ sSectorX ][ sSectorY ] ) @@ -4450,44 +3859,31 @@ void ShowPeopleInMotion( INT16 sX, INT16 sY ) INT16 sSource = 0; INT16 sOffsetX = 0, sOffsetY = 0; INT16 iX = sX, iY = sY; - INT16 sXPosition = 0, sYPosition = 0; - INT32 iCounter = 0; + INT32 iDirection = 0; HVOBJECT hIconHandle; BOOLEAN fAboutToEnter = FALSE; - CHAR16 sString[ 32 ]; - INT16 sTextXOffset = 0; - INT16 sTextYOffset = 0; - INT16 usX, usY; INT32 iWidth = 0, iHeight = 0; INT32 iDeltaXForError = 0, iDeltaYForError = 0; + UINT16 usArrowWidth; + UINT16 usArrowHeight; + INT32 MAP_MVT_ICON_FONT; + if (iResolution < _800x600 ) + { + MAP_MVT_ICON_FONT = TINYFONT1; + } + else + { + MAP_MVT_ICON_FONT = FONT10ARIAL; + } if( iCurrentMapSectorZ != 0 ) { return; } - UINT8 iconOffsetX = 0; - UINT8 iconOffsetY = 0; - - if (iResolution >= _640x480 && iResolution < _800x600) - { - iconOffsetX = 2; - iconOffsetY = 1; - } - else if (iResolution < _1024x768) - { - iconOffsetX = 3; - iconOffsetY = 2; - } - else - { - iconOffsetX = 4; - iconOffsetY = 3; - } - // show the icons for people in motion from this sector to the next guy over - for( iCounter = 0; iCounter < 4; iCounter++ ) + for( iDirection = 0; iDirection < 4; iDirection++ ) { // find how many people are coming and going in this sector sExiting = 0; @@ -4503,19 +3899,19 @@ void ShowPeopleInMotion( INT16 sX, INT16 sY ) sDest = sSource; - if( ( iCounter == 0 ) && sY > MINIMUM_VALID_Y_COORDINATE ) + if( ( iDirection == 0 ) && sY > MINIMUM_VALID_Y_COORDINATE ) { sDest += NORTH_MOVE; } - else if( ( iCounter == 1 ) && ( sX < MAXIMUM_VALID_X_COORDINATE ) ) + else if( ( iDirection == 1 ) && ( sX < MAXIMUM_VALID_X_COORDINATE ) ) { sDest += EAST_MOVE; } - else if( ( iCounter == 2 ) && ( sY < MAXIMUM_VALID_Y_COORDINATE ) ) + else if( ( iDirection == 2 ) && ( sY < MAXIMUM_VALID_Y_COORDINATE ) ) { sDest += SOUTH_MOVE; } - else if( ( iCounter == 3 ) && ( sX > MINIMUM_VALID_X_COORDINATE ) ) + else if( ( iDirection == 3 ) && ( sX > MINIMUM_VALID_X_COORDINATE ) ) { sDest += WEST_MOVE; } @@ -4527,140 +3923,466 @@ void ShowPeopleInMotion( INT16 sX, INT16 sY ) int tYS = sSource / MAP_WORLD_X; int tXD = sDest % MAP_WORLD_X; int tYD = sDest / MAP_WORLD_X; - if( PlayersBetweenTheseSectors( ( INT16 ) SECTOR(tXS, tYS) , ( INT16 ) SECTOR(tXD, tYD), &sExiting, &sEntering, &fAboutToEnter ) ) + + // Find sector's top-left pixel on the screen. + INT16 sSectorPosX = MAP_VIEW_START_X + (sX * MAP_GRID_X); + INT16 sSectorPosY = MAP_VIEW_START_Y + (sY * MAP_GRID_Y); + + // This is the sectorID in plain 0-256. Most of the functions will use this instead of sSource and sDest. + INT16 sSourceSector = SECTOR( tXS, tYS ); + INT16 sDestSector = SECTOR( tXD, tYD ); + + // HEADROCK HAM 5: We are no longer running a subfunction (PlayersBetweenTheseSectors()) + // to determine whether mercs are moving between these sectors. Instead, we'll go through + // each group, and if it is moving out of the currently-checked sector, we'll draw an arrow + // for it. + // + // Each arrow denotes one group, and its color and position will reflect how close the group + // is to entering its target sector. + // + // Additionally we also check for groups traveling in the other direction, as we'll want to + // offset the arrow appropriately. + + // Current group being checked. + GROUP *curr; + // Sector of current battle + INT16 sBattleSector = -1; + BOOLEAN fMayRetreatFromBattle = FALSE; + BOOLEAN fRetreatingFromBattle = FALSE; + BOOLEAN fHelicopterGroup = FALSE; + BOOLEAN fEntering = FALSE; + BOOLEAN fDrawOnlyOne = FALSE; + + // Find any battle raging at the moment. + if( gpBattleGroup ) { - // someone is leaving + // Get its sector + sBattleSector = (INT16)SECTOR( gpBattleGroup->ubSectorX, gpBattleGroup->ubSectorY ); + } - // now find position - if( !( iCounter % 2 ) ) + // If we are showing escape routes from the battle sector, we'll need to handle the arrows + // differently. + BOOLEAN fShowingPossibleRetreatInThisDirection = FALSE; + if (gfDisplayPotentialRetreatPaths) + { + // Is our currently-examined source sector the battle sector? + if ( sBattleSector == sSourceSector ) { - // guys going north or south - if( sEntering > 0 ) + curr = gpGroupList; + // If so, is any of the groups in the sector able to retreat in the current direction? + while (curr) { - // more than one coming in, offset from middle - sOffsetX = ( !iCounter ? ( !fZoomFlag ? NORTH_X_MVT_OFFSET: NORTH_X_MVT_OFFSET_ZOOM ) : ( !fZoomFlag ? SOUTH_X_MVT_OFFSET: SOUTH_X_MVT_OFFSET_ZOOM ) ); + if (curr->fPlayer == TRUE && // Player group + curr->ubSectorX == sX && curr->ubSectorY == sY && // Group is in the battle sector + curr->ubPrevX == tXD && curr->ubPrevY == tYD) // Group came from the examined destination + { + // Not helicopter! + if ( !IsGroupTheHelicopterGroup( curr ) ) + { + // Draw only one arrow exiting this sector in the current direction, in orange. + fShowingPossibleRetreatInThisDirection = TRUE; + } + } + curr = curr->next; } - else + } + // Is our currently-examined destination sector the battle sector? + else if ( sBattleSector == sDestSector ) + { + // If so, is any of the groups there capable of retreating in the opposite direction? + curr = gpGroupList; + while (curr) { - sOffsetX = ( !fZoomFlag ? NORTH_SOUTH_CENTER_OFFSET : NORTH_SOUTH_CENTER_OFFSET_ZOOM ); + if (curr->fPlayer == TRUE && // Player group + curr->ubSectorX == tXD && curr->ubSectorY == tYD && // Group is in the battle sector + curr->ubPrevX == sX && curr->ubPrevY == sY) // Group came from the examined source + { + // Not helicopter! + if ( !IsGroupTheHelicopterGroup( curr ) ) + { + // We've got an arrow entering the sector. + fEntering = TRUE; + } + } + curr = curr->next; } + } + } + // If none of the flags have been set yet, check for groups entering the sector at this time. + if (!fEntering) + { + curr = gpGroupList; + while (curr) + { + if (curr->fPlayer == TRUE && // Player group + curr->ubNextX == tXS && curr->ubNextY == tYS && // Heading into this sector + !IsGroupTheHelicopterGroup( curr ) ) // Not a helicopter group! + { + // There are people entering from this direction + fEntering = TRUE; + } + curr = curr->next; + } + } + + // Offset the arrows exiting this sector so that they don't overlap with the arrows entering the + // sector. - if( !iCounter ) + GetVideoObject(&hIconHandle, guiCHARBETWEENSECTORICONS ); + + usArrowWidth = hIconHandle->pETRLEObject[ iDirection ].usWidth; + usArrowHeight = hIconHandle->pETRLEObject[ iDirection ].usHeight; + + if( !( iDirection % 2 ) ) + { + // guys exiting north or south from this sector. Center the arrow. + sOffsetX = (MAP_GRID_X / 2); + + if( fEntering > 0 ) + { + // A team is also entering this sector from the same direction. + + // Is the team in this sector heading north? + if (iDirection == 0) { - // going north - sOffsetY = ( !fZoomFlag ? NORTH_Y_MVT_OFFSET : NORTH_Y_MVT_OFFSET_ZOOM ); + sOffsetX += 1; } else { - // going south - sOffsetY = ( !fZoomFlag ? SOUTH_Y_MVT_OFFSET : SOUTH_Y_MVT_OFFSET_ZOOM ); + sOffsetX -= usArrowWidth+1; } } else { - // going east/west + // Center the arrow along the X axis. + sOffsetX -= (usArrowWidth / 2); + } - if( sEntering > 0 ) + if( iDirection == 0 ) + { + // going north + sOffsetY = -usArrowHeight; + } + else + { + // going south + sOffsetY = MAP_GRID_Y; + } + } + else + { + // guys exiting east or west from this sector. Center the arrow. + + sOffsetY = (MAP_GRID_Y / 2); + + if( fEntering > 0 ) + { + // A team is also entering this sector from the same direction. + + // Is the team in this sector heading east? + if (iDirection == 1) { - // people also entering, offset from middle - sOffsetY = ( iCounter == 1? ( !fZoomFlag ? EAST_Y_MVT_OFFSET: EAST_Y_MVT_OFFSET_ZOOM ) : ( !fZoomFlag ? WEST_Y_MVT_OFFSET: WEST_Y_MVT_OFFSET_ZOOM ) ); + sOffsetY += 1; } else { - sOffsetY = ( !fZoomFlag ? EAST_WEST_CENTER_OFFSET : EAST_WEST_CENTER_OFFSET_ZOOM ); + sOffsetY -= usArrowHeight+1; } + } + else + { + // Center the arrow along the Y axis. + sOffsetY -= (usArrowHeight / 2); + } - if( iCounter == 1 ) + if( iDirection == 1 ) + { + // going east + sOffsetX = MAP_GRID_X; + } + else + { + // going west + sOffsetX = -usArrowWidth; + } + } + + // Have we found a group that can retreat in this direction? + if (fShowingPossibleRetreatInThisDirection) + { + // Draw only its arrow, in orange. + + iX = sSectorPosX + sOffsetX; + iY = (sSectorPosY + sOffsetY) -1; + + hIconHandle->p16BPPPalette[2] = Get16BPPColor( (UINT16)FROMRGB( 244, 103, 245 ) ) ; + hIconHandle->p16BPPPalette[3] = Get16BPPColor( (UINT16)FROMRGB( 239, 182, 143 ) ) ; + hIconHandle->p16BPPPalette[4] = Get16BPPColor( (UINT16)FROMRGB( 234, 158, 105 ) ) ; + hIconHandle->p16BPPPalette[5] = Get16BPPColor( (UINT16)FROMRGB( 225, 118, 45 ) ) ; + + BltVideoObject(guiSAVEBUFFER, hIconHandle, ( UINT16 )iDirection , ( INT16 ) iX, ( INT16 ) iY , VO_BLT_SRCTRANSPARENCY, NULL ); + + // We've drawn the only arrow we need to, so continue to check the next direction. + continue; + } + + // At this point we know that this direction should be checked normally, for any groups currently leaving it. + + // First lets set some basic values depending on direction. + INT16 sNoProgressOffsetX = 0; + INT16 sNoProgressOffsetY = 0; + if (iDirection == 0) + { + sNoProgressOffsetY = usArrowHeight; + } + else if (iDirection == 1) + { + sNoProgressOffsetX = -usArrowWidth; + } + else if (iDirection == 2) + { + sNoProgressOffsetY = -usArrowHeight; + } + else if (iDirection == 3) + { + sNoProgressOffsetX = usArrowWidth; + } + + // We run through all groups one by one, and find the group with the longest travel time remaining. We draw + // an arrow for it, and then check for the group with the next longest travel time, and so on until we have + // no more arrows to draw. + + // check all groups + BOOLEAN fMoreGroupsToCheck = TRUE; + BOOLEAN fFoundFirstGroup = FALSE; + FLOAT dLastTravelProgress = 10000.0f; // Stores the fractional travel time for the last group drawn. + FLOAT dFirstTravelProgress = 0; + INT32 dLastTravelTimeRemaining = 0; + while (fMoreGroupsToCheck) + { + fMoreGroupsToCheck = FALSE; // Assume there are no more groups until we find them. + + // If we have not yet found the group with the least progress... + if (!fFoundFirstGroup) + { + curr = gpGroupList; + while( curr ) { - // going east - sOffsetX = ( !fZoomFlag ? EAST_X_MVT_OFFSET : EAST_X_MVT_OFFSET_ZOOM ); + if (curr->fPlayer == TRUE && // Player group + curr->fBetweenSectors == TRUE && // Currently in motion + SECTOR(curr->ubSectorX, curr->ubSectorY) == sSourceSector && // Heading out of this sector + SECTOR(curr->ubNextX, curr->ubNextY) == sDestSector ) // Heading to the destination we're examining + { + if (!IsGroupTheHelicopterGroup( curr ) ) // Not a helicopter group! + { + // How long does it take to travel from source to dest? + INT32 iFullTravelTime = curr->uiTraverseTime; + // How far is this group from the starting point? + INT32 iCurTravelTime = iFullTravelTime - (curr->uiArrivalTime - GetWorldTotalMin( )); + + iCurTravelTime = __max(0, iCurTravelTime); + iCurTravelTime = __min(iFullTravelTime, iCurTravelTime); + + // If the group is just starting to move, this will be 0.0. + // If it is almsot at the destination, this will be close to 1.0. + FLOAT dCurTravelProgress = (FLOAT)iCurTravelTime / (FLOAT)iFullTravelTime; + + // Is this the group with the most distance to the destination? + if ( dCurTravelProgress < dLastTravelProgress ) + { + // If this isn't the first group we've found... + if (dLastTravelProgress < 10000.0f) + { + // We've skipped at least one group. Set the flag so we know to check for + // more groups later. + fMoreGroupsToCheck = TRUE; + } + + // Ok, this is the group with the most distance to travel thus far. + // Lets store the data. + dLastTravelProgress = dCurTravelProgress; + } + else + { + // Found a group that needs to be checked later. Set the flag. + fMoreGroupsToCheck = TRUE; + } + } + } + curr = curr->next; + } + if (dLastTravelProgress < 10000.0f) + { + // We've found the first group - i.e. the group with the longest travel time remaining. + fFoundFirstGroup = TRUE; + + FLOAT dProgressForDrawing = dLastTravelProgress; + if (dProgressForDrawing < 0.49f) + { + dProgressForDrawing = __max(0.0f, dProgressForDrawing - 0.1f); // Bias downwards. + } + else if (dProgressForDrawing > 0.51f) + { + dProgressForDrawing = __min(1.0f, dProgressForDrawing + 0.1f); // Bias upwards. + } + + INT16 sProgressOffsetX = (INT16)((1.0f-dProgressForDrawing) * (FLOAT)sNoProgressOffsetX); + INT16 sProgressOffsetY = (INT16)((1.0f-dProgressForDrawing) * (FLOAT)sNoProgressOffsetY); + + UINT8 ubDark[3]; + UINT8 ubMid[3]; + UINT8 ubLight[3]; + UINT8 ubLighter[3]; + + ubLighter[0] = (UINT8)(173 + ((FLOAT)(247 - 173) * dProgressForDrawing)); + ubLighter[1] = (UINT8)(199 + ((FLOAT)(245 - 199) * dProgressForDrawing)); + ubLighter[2] = (UINT8)(247 + ((FLOAT)(173 - 247) * dProgressForDrawing)); + + ubLight[0] = (UINT8)(148 + ((FLOAT)(239 - 148) * dProgressForDrawing)); + ubLight[1] = (UINT8)(178 + ((FLOAT)(249 - 178) * dProgressForDrawing)); + ubLight[2] = (UINT8)(239 + ((FLOAT)(138 - 239) * dProgressForDrawing)); + + ubMid[0] = (UINT8)(107 + ((FLOAT)(228 - 107) * dProgressForDrawing)); + ubMid[1] = (UINT8)(146 + ((FLOAT)(231 - 146) * dProgressForDrawing)); + ubMid[2] = (UINT8)(231 + ((FLOAT)(107 - 231) * dProgressForDrawing)); + + ubDark[0] = (UINT8)(82 + ((FLOAT)(179 - 82) * dProgressForDrawing)); + ubDark[1] = (UINT8)(113 + ((FLOAT)(181 - 113) * dProgressForDrawing)); + ubDark[2] = (UINT8)(181 + ((FLOAT)(82 - 181) * dProgressForDrawing)); + + hIconHandle->p16BPPPalette[2] = Get16BPPColor( (UINT32)FROMRGB( ubLighter[0], ubLighter[1], ubLighter[2] ) ) ; + hIconHandle->p16BPPPalette[3] = Get16BPPColor( (UINT32)FROMRGB( ubLight[0], ubLight[1], ubLight[2] ) ) ; + hIconHandle->p16BPPPalette[4] = Get16BPPColor( (UINT32)FROMRGB( ubMid[0], ubMid[1], ubMid[2] ) ) ; + hIconHandle->p16BPPPalette[5] = Get16BPPColor( (UINT32)FROMRGB( ubDark[0], ubDark[1], ubDark[2] ) ) ; + + iX = sSectorPosX + sOffsetX + sProgressOffsetX; + iY = ((sSectorPosY + sOffsetY) -1) + sProgressOffsetY; + + BltVideoObject(guiSAVEBUFFER, hIconHandle, ( UINT16 )iDirection , ( INT16 ) iX, ( INT16 ) iY , VO_BLT_SRCTRANSPARENCY, NULL ); } else { - // going west - sOffsetX = ( !fZoomFlag ? WEST_X_MVT_OFFSET : WEST_X_MVT_OFFSET_ZOOM ); + // No squads moving at all! Because the flag is set, this will end the check. + fMoreGroupsToCheck = FALSE; + continue; } + + // Store the progress of the first group we've found. + dFirstTravelProgress = dLastTravelProgress; } - switch( iCounter ) - { - case 0: - sTextXOffset = NORTH_TEXT_X_OFFSET; - sTextYOffset = NORTH_TEXT_Y_OFFSET; - break; - case 1: - sTextXOffset = EAST_TEXT_X_OFFSET; - sTextYOffset = EAST_TEXT_Y_OFFSET; - break; - case 2: - sTextXOffset = SOUTH_TEXT_X_OFFSET; - sTextYOffset = SOUTH_TEXT_Y_OFFSET; - break; - case 3: - sTextXOffset = WEST_TEXT_X_OFFSET; - sTextYOffset = WEST_TEXT_Y_OFFSET; - break; - } - - - // blit the text - - SetFont( MAP_MVT_ICON_FONT ); - - if( !fAboutToEnter ) - { - SetFontForeground( FONT_WHITE ); - } + // If we've already found the first group, we need to look for the next group that is closer to the + // destination. else { - SetFontForeground( FONT_BLACK ); + // Run through the list again. + curr = gpGroupList; + dLastTravelProgress = 10000.0f; + while( curr ) + { + if (curr->fPlayer == TRUE && // Player group + curr->fBetweenSectors == TRUE && // Currently in motion + SECTOR(curr->ubSectorX, curr->ubSectorY) == sSourceSector && // Heading out of this sector + SECTOR(curr->ubNextX, curr->ubNextY) == sDestSector ) // Heading to the destination we're examining + { + if (!IsGroupTheHelicopterGroup( curr ) ) // Not a helicopter group! + { + // How long does it take to travel from source to dest? + INT32 iFullTravelTime = curr->uiTraverseTime; + // How far is this group from the starting point? + INT32 iCurTravelTime = iFullTravelTime - (curr->uiArrivalTime - GetWorldTotalMin( )); + + iCurTravelTime = __max(0, iCurTravelTime); + iCurTravelTime = __min(iFullTravelTime, iCurTravelTime); + + // If the group is just starting to move, this will be 0.0. + // If it is almsot at the destination, this will be close to 1.0. + FLOAT dCurTravelProgress = (FLOAT)iCurTravelTime / (FLOAT)iFullTravelTime; + + // Check this group's travel time against the last group we've drawn an arrow for. + if ( dCurTravelProgress > dFirstTravelProgress && // This group is closer to the destination than the previous one + dCurTravelProgress < dLastTravelProgress ) // And is the next furthest one from the destination so far + { + // If this isn't the first group we've found... + if (dLastTravelProgress < 10000.0f) + { + // We've skipped at least one group. Set the flag so we know to check for + // more groups later. + fMoreGroupsToCheck = TRUE; + } + + // Brilliant, lets store the data. + dLastTravelProgress = dCurTravelProgress; + } + // Is this group nonetheless closer to the destination than the last group we drew + // an arrow for? + else if (dCurTravelProgress > dFirstTravelProgress) + { + // Found a group that needs to be checked later. Set the flag. + fMoreGroupsToCheck = TRUE; + } + } + } + curr = curr->next; + } + if (dLastTravelProgress < 10000.0f) + { + FLOAT dProgressForDrawing = dLastTravelProgress; + if (dProgressForDrawing < 0.49f) + { + dProgressForDrawing = __max(0.0f, dProgressForDrawing - 0.1f); // Bias downwards. + } + else if (dProgressForDrawing > 0.51f) + { + dProgressForDrawing = __min(1.0f, dProgressForDrawing + 0.1f); // Bias upwards. + } + + INT16 sProgressOffsetX = (INT16)((1.0f-dProgressForDrawing) * (FLOAT)sNoProgressOffsetX); + INT16 sProgressOffsetY = (INT16)((1.0f-dProgressForDrawing) * (FLOAT)sNoProgressOffsetY); + + UINT8 ubDark[3]; + UINT8 ubMid[3]; + UINT8 ubLight[3]; + UINT8 ubLighter[3]; + + ubLighter[0] = (UINT8)(173 + ((FLOAT)(247 - 173) * dProgressForDrawing)); + ubLighter[1] = (UINT8)(199 + ((FLOAT)(245 - 199) * dProgressForDrawing)); + ubLighter[2] = (UINT8)(247 + ((FLOAT)(173 - 247) * dProgressForDrawing)); + + ubLight[0] = (UINT8)(148 + ((FLOAT)(239 - 148) * dProgressForDrawing)); + ubLight[1] = (UINT8)(178 + ((FLOAT)(249 - 178) * dProgressForDrawing)); + ubLight[2] = (UINT8)(239 + ((FLOAT)(138 - 239) * dProgressForDrawing)); + + ubMid[0] = (UINT8)(107 + ((FLOAT)(228 - 107) * dProgressForDrawing)); + ubMid[1] = (UINT8)(146 + ((FLOAT)(231 - 146) * dProgressForDrawing)); + ubMid[2] = (UINT8)(231 + ((FLOAT)(107 - 231) * dProgressForDrawing)); + + ubDark[0] = (UINT8)(82 + ((FLOAT)(179 - 82) * dProgressForDrawing)); + ubDark[1] = (UINT8)(113 + ((FLOAT)(181 - 113) * dProgressForDrawing)); + ubDark[2] = (UINT8)(181 + ((FLOAT)(82 - 181) * dProgressForDrawing)); + + hIconHandle->p16BPPPalette[2] = Get16BPPColor( (UINT32)FROMRGB( ubLighter[0], ubLighter[1], ubLighter[2] ) ) ; + hIconHandle->p16BPPPalette[3] = Get16BPPColor( (UINT32)FROMRGB( ubLight[0], ubLight[1], ubLight[2] ) ) ; + hIconHandle->p16BPPPalette[4] = Get16BPPColor( (UINT32)FROMRGB( ubMid[0], ubMid[1], ubMid[2] ) ) ; + hIconHandle->p16BPPPalette[5] = Get16BPPColor( (UINT32)FROMRGB( ubDark[0], ubDark[1], ubDark[2] ) ) ; + + iX = sSectorPosX + sOffsetX + sProgressOffsetX; + iY = ((sSectorPosY + sOffsetY) -1) + sProgressOffsetY; + + BltVideoObject(guiSAVEBUFFER, hIconHandle, ( UINT16 )iDirection , ( INT16 ) iX, ( INT16 ) iY , VO_BLT_SRCTRANSPARENCY, NULL ); + } + else + { + // No squads moving at all! Because the flag is set, this will end the check. + fMoreGroupsToCheck = FALSE; + continue; + } + dFirstTravelProgress = dLastTravelProgress; } - SetFontBackground( FONT_BLACK ); - swprintf( sString, L"%d", sExiting ); - - // about to enter - if( !fAboutToEnter ) - { - // draw blue arrows - GetVideoObject(&hIconHandle, guiCHARBETWEENSECTORICONS ); - } - else - { - // draw yellow arrows - GetVideoObject(&hIconHandle, guiCHARBETWEENSECTORICONSCLOSE ); - } - - // zoomed in or not? - if(!fZoomFlag) - { - iX = MAP_VIEW_START_X+( iX * MAP_GRID_X ) + sOffsetX; - iY = iconOffsetY + MAP_VIEW_START_Y + ( iY * MAP_GRID_Y ) + sOffsetY; - - BltVideoObject(guiSAVEBUFFER, hIconHandle, ( UINT16 )iCounter , ( INT16 ) iX, ( INT16 ) iY , VO_BLT_SRCTRANSPARENCY, NULL ); - } - else - { - GetScreenXYFromMapXYStationary( ((UINT16)(iX)),((UINT16)(iY)) , &sXPosition, &sYPosition ); - - iY=sYPosition-MAP_GRID_Y + sOffsetY; - iX=sXPosition-MAP_GRID_X + sOffsetX; - - // clip blits to mapscreen region - ClipBlitsToMapViewRegion( ); - - BltVideoObject(guiSAVEBUFFER, hIconHandle,( UINT16 )iCounter , iX , iY , VO_BLT_SRCTRANSPARENCY, NULL ); - - // restore clip blits - RestoreClipRegionToFullScreen( ); - } - - FindFontCenterCoordinates(( INT16 )( iX + sTextXOffset ), 0, ICON_WIDTH, 0, sString, MAP_FONT, &usX, &usY); - SetFontDestBuffer( guiSAVEBUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); - mprintf( usX, iY + sTextYOffset, sString); - - switch( iCounter % 2 ) + /* + switch( iDirection % 2 ) { case 0 : // north south @@ -4694,17 +4416,155 @@ void ShowPeopleInMotion( INT16 sX, INT16 sY ) { RestoreExternBackgroundRect( iX, iY, ( INT16 )iWidth, ( INT16 )iHeight ); } + */ } } } // restore buffer SetFontDestBuffer( FRAME_BUFFER, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, FALSE ); - } +// HEADROCK HAM 5.1: Show enemy group destinations. +void ShowEnemyGroupsInMotion( INT16 sX, INT16 sY ) +{ + INT32 sExiting = 0; + INT32 sEntering = 0; + INT16 sDest = 0; + INT16 sSource = 0; + INT16 sOffsetX = 0, sOffsetY = 0; + INT16 sXPosition = 0, sYPosition = 0; + INT32 iCounter = 0; + HVOBJECT hIconHandle; + INT16 sTextXOffset = 0; + INT16 sTextYOffset = 0; + INT32 iWidth = 0, iHeight = 0; + INT32 iDeltaXForError = 0, iDeltaYForError = 0; + if( iCurrentMapSectorZ != 0 ) + { + return; + } + + // show the icons for people in motion from this sector to the next guy over + + GROUP *curr; + curr = gpGroupList; + while( curr->next ) + { + if (!curr->fPlayer && curr->ubSectorX == sX && curr->ubSectorY == sY && curr->ubSectorZ == 0) + { + if (curr->ubNextX != sX || curr->ubNextY != sY) + { + if (curr->ubNextX >= MINIMUM_VALID_X_COORDINATE && curr->ubNextX <= MAXIMUM_VALID_X_COORDINATE && + curr->ubNextY >= MINIMUM_VALID_Y_COORDINATE && curr->ubNextY <= MAXIMUM_VALID_Y_COORDINATE ) // 3 hours to arrival, tops + { + INT16 iX = sX, iY = sY; + UINT8 ubDirection = 0; + INT16 sDeltaX = curr->ubNextX - sX; + INT16 sDeltaY = curr->ubNextY - sY; + + GetVideoObject(&hIconHandle, guiENEMYBETWEENSECTORICONS ); + + INT16 iconOffsetX = 0; + INT16 iconOffsetY = 0; + + if (sDeltaX != 0 && sDeltaY != 0) + { + // Group moving diagonally? Abort. + continue; + } + if (sDeltaY < 0) + { + ubDirection = 0; + sOffsetX = (MAP_GRID_X / 2); + sOffsetY = 0; + iconOffsetX = -(hIconHandle->pETRLEObject[ubDirection].usWidth / 2); + iconOffsetY = -(hIconHandle->pETRLEObject[ubDirection].usHeight); + } + else if (sDeltaX > 0) + { + ubDirection = 1; + sOffsetX = MAP_GRID_X; + sOffsetY = (MAP_GRID_Y / 2); + iconOffsetY = -(hIconHandle->pETRLEObject[ubDirection].usHeight / 2); + } + else if (sDeltaY > 0) + { + ubDirection = 2; + sOffsetX = (MAP_GRID_X / 2); + sOffsetY = MAP_GRID_Y; + iconOffsetX = -(hIconHandle->pETRLEObject[ubDirection].usWidth / 2); + } + else if (sDeltaX < 0) + { + ubDirection = 3; + sOffsetX = 0; + sOffsetY = (MAP_GRID_Y / 2); + iconOffsetX = -(hIconHandle->pETRLEObject[ubDirection].usWidth); + iconOffsetY = -(hIconHandle->pETRLEObject[ubDirection].usHeight / 2); + } + + if (iResolution >= _1024x768 ) + { + iconOffsetY -= 1; + } + + // zoomed in or not? + if(!fZoomFlag) + { + iX = MAP_VIEW_START_X+( sX * MAP_GRID_X ) + sOffsetX + iconOffsetX; + iY = MAP_VIEW_START_Y + ( sY * MAP_GRID_Y ) + sOffsetY + iconOffsetY; + + BltVideoObject(guiSAVEBUFFER, hIconHandle, ubDirection , ( INT16 ) iX, ( INT16 ) iY , VO_BLT_SRCTRANSPARENCY, NULL ); + } + else + { + GetScreenXYFromMapXYStationary( ((UINT16)(iX)),((UINT16)(iY)) , &sXPosition, &sYPosition ); + + iY=sYPosition-MAP_GRID_Y + sOffsetY; + iX=sXPosition-MAP_GRID_X + sOffsetX; + + // clip blits to mapscreen region + ClipBlitsToMapViewRegion( ); + + BltVideoObject(guiSAVEBUFFER, hIconHandle, ubDirection , iX , iY , VO_BLT_SRCTRANSPARENCY, NULL ); + + // restore clip blits + RestoreClipRegionToFullScreen( ); + } + + iWidth = 12; + iHeight = 7; + + // error correction for scrolling with people on the move + if( iX < 0 ) + { + iDeltaXForError = 0 - iX; + iWidth -= iDeltaXForError; + iX = 0; + } + + if( iY < 0 ) + { + iDeltaYForError = 0 - iY; + iHeight -= iDeltaYForError; + iY = 0; + } + + if( ( iWidth > 0 )&&( iHeight > 0 ) ) + { + RestoreExternBackgroundRect( iX, iY, ( INT16 )iWidth, ( INT16 )iHeight ); + } + } + } + } + curr = curr->next; + } + +} + void DisplayDistancesForHelicopter( void ) { // calculate the distance travelled, the proposed distance, and total distance one can go @@ -4859,20 +4719,34 @@ void DisplayPositionOfHelicopter( void ) GROUP *pGroup; HVOBJECT hHandle; INT32 iNumberOfPeopleInHelicopter = 0; - CHAR16 sString[ 4 ]; + + INT32 MAP_MVT_ICON_FONT = TINYFONT1; AssertMsg( ( sOldMapX >= 0 ) && ( sOldMapX < SCREEN_WIDTH ), String( "DisplayPositionOfHelicopter: Invalid sOldMapX = %d", sOldMapX ) ); AssertMsg( ( sOldMapY >= 0 ) && ( sOldMapY < SCREEN_HEIGHT ), String( "DisplayPositionOfHelicopter: Invalid sOldMapY = %d", sOldMapY ) ); + + // HEADROCK HAM 5: Now has to be done here to get the size of the helicopter icon. + GetVideoObject( &hHandle, guiHelicopterIcon ); + + UINT16 usIconWidth = hHandle->pETRLEObject[ 0 ].usWidth; + UINT16 usIconHeight = hHandle->pETRLEObject[ 0 ].usHeight; + UINT16 usIconOffsetX = hHandle->pETRLEObject[ 0 ].sOffsetX; + UINT16 usIconOffsetY = hHandle->pETRLEObject[ 0 ].sOffsetY; + // restore background on map where it is - if( sOldMapX != 0 ) { - RestoreExternBackgroundRect( sOldMapX, sOldMapY, HELI_ICON_WIDTH, HELI_ICON_HEIGHT ); + if( sOldMapX != 0 ) + { + RestoreExternBackgroundRect( sOldMapX + usIconOffsetX, sOldMapY + usIconOffsetY, usIconWidth, usIconHeight ); sOldMapX = 0; } if( iHelicopterVehicleId != -1 ) { // draw the destination icon first, so when they overlap, the real one is on top! - DisplayDestinationOfHelicopter( ); + // HEADROCK HAM 5: No reason to display this anymore: we now do it with a cool new icon displayed + // while drawing the path. + + //DisplayDestinationOfHelicopter( ); // check if mvt group if( pVehicleList[ iHelicopterVehicleId ].ubMovementGroup != 0 ) { @@ -4924,8 +4798,9 @@ void DisplayPositionOfHelicopter( void ) x = ( UINT32 )( minX + flRatio * ( ( INT16 ) maxX - ( INT16 ) minX ) ); y = ( UINT32 )( minY + flRatio * ( ( INT16 ) maxY - ( INT16 ) minY ) ); - x += 1; - y += 3; + // HEADROCK HAM 5: Not necessary? + //x += 1; + //y += 3; AssertMsg( ( x >= 0 ) && ( x < (UINT32)SCREEN_WIDTH ), String( "DisplayPositionOfHelicopter: Invalid x = %d. At %d,%d. Next %d,%d. Min/Max X = %d/%d", x, pGroup->ubSectorX, pGroup->ubSectorY, pGroup->ubNextX, pGroup->ubNextY, minX, maxX ) ); @@ -4934,7 +4809,8 @@ void DisplayPositionOfHelicopter( void ) // clip blits to mapscreen region ClipBlitsToMapViewRegion( ); - + // HEADROCK HAM 5: Not necessary? + /* GetVideoObject( &hHandle, guiHelicopterIcon ); if (iResolution >= _800x600) @@ -4942,10 +4818,14 @@ void DisplayPositionOfHelicopter( void ) x = x + (MAP_GRID_X / 2) - 10; y = y + 1 + (MAP_GRID_Y / 2) - 10; } + */ BltVideoObject( FRAME_BUFFER, hHandle, HELI_ICON, x, y, VO_BLT_SRCTRANSPARENCY, NULL ); + // HEADROCK HAM 5: Do not draw anymore. // now get number of people and blit that too + + /* iNumberOfPeopleInHelicopter = GetNumberOfPassengersInHelicopter( ); swprintf( sString, L"%d", iNumberOfPeopleInHelicopter ); @@ -4954,8 +4834,9 @@ void DisplayPositionOfHelicopter( void ) SetFontBackground( FONT_BLACK ); mprintf( x + 5, y + 1 , sString ); + */ - InvalidateRegion( x, y, x + HELI_ICON_WIDTH, y + HELI_ICON_HEIGHT ); + //InvalidateRegion( x, y, x + usIconWidth, y + usIconHeight ); RestoreClipRegionToFullScreen( ); @@ -5005,6 +4886,7 @@ void DisplayDestinationOfHelicopter( void ) ClipBlitsToMapViewRegion( ); GetVideoObject( &hHandle, guiHelicopterIcon ); + BltVideoObject( FRAME_BUFFER, hHandle, HELI_SHADOW_ICON, x, y, VO_BLT_SRCTRANSPARENCY, NULL ); if (iResolution >= _800x600) @@ -5160,7 +5042,18 @@ void BlitMineText( INT16 sMapX, INT16 sMapY ) SetFontDestBuffer( guiSAVEBUFFER, MAP_VIEW_START_X, MAP_VIEW_START_Y, MAP_VIEW_START_X+MAP_VIEW_WIDTH+MAP_GRID_X, MAP_VIEW_START_Y+MAP_VIEW_HEIGHT+7, FALSE ); - SetFont(MAP_FONT); + // HEADROCK HAM 5: Variable Font + INT32 MapMineLabelsFont; + if (iResolution <= _800x600 ) + { + MapMineLabelsFont = MAP_FONT; + } + else + { + MapMineLabelsFont = FONT14ARIAL; + } + + SetFont(MapMineLabelsFont); SetFontForeground( FONT_LTGREEN ); SetFontBackground( FONT_BLACK ); @@ -5169,8 +5062,8 @@ void BlitMineText( INT16 sMapX, INT16 sMapY ) // display associated town name, followed by "mine" swprintf( wString, L"%s %s", pTownNames[ GetTownAssociatedWithMine( GetMineIndexForSector( sMapX, sMapY ) ) ], pwMineStrings[ 0 ] ); - AdjustXForLeftMapEdge(wString, &sScreenX); - mprintf( ( sScreenX - StringPixLength( wString, MAP_FONT ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MAP_FONT ) , wString ); + AdjustXForLeftMapEdge(wString, &sScreenX, MapMineLabelsFont); + mprintf( ( sScreenX - StringPixLength( wString, MapMineLabelsFont ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MapMineLabelsFont ) , wString ); ubLineCnt++; @@ -5178,24 +5071,24 @@ void BlitMineText( INT16 sMapX, INT16 sMapY ) if (gMineStatus[ ubMineIndex ].fEmpty) { swprintf( wString, L"%s", pwMineStrings[ 5 ] ); - AdjustXForLeftMapEdge(wString, &sScreenX); - mprintf( ( sScreenX - StringPixLength( wString, MAP_FONT ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MAP_FONT ) , wString ); + AdjustXForLeftMapEdge(wString, &sScreenX, MapMineLabelsFont); + mprintf( ( sScreenX - StringPixLength( wString, MapMineLabelsFont ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MapMineLabelsFont ) , wString ); ubLineCnt++; } else if (gMineStatus[ ubMineIndex ].fShutDown) { swprintf( wString, L"%s", pwMineStrings[ 6 ] ); - AdjustXForLeftMapEdge(wString, &sScreenX); - mprintf( ( sScreenX - StringPixLength( wString, MAP_FONT ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MAP_FONT ) , wString ); + AdjustXForLeftMapEdge(wString, &sScreenX, MapMineLabelsFont); + mprintf( ( sScreenX - StringPixLength( wString, MapMineLabelsFont ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MapMineLabelsFont ) , wString ); ubLineCnt++; } else if (gMineStatus[ ubMineIndex ].fRunningOut) { swprintf( wString, L"%s", pwMineStrings[ 7 ] ); - AdjustXForLeftMapEdge(wString, &sScreenX); - mprintf( ( sScreenX - StringPixLength( wString, MAP_FONT ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MAP_FONT ) , wString ); + AdjustXForLeftMapEdge(wString, &sScreenX, MapMineLabelsFont); + mprintf( ( sScreenX - StringPixLength( wString, MapMineLabelsFont ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MapMineLabelsFont ) , wString ); ubLineCnt++; } @@ -5230,8 +5123,8 @@ void BlitMineText( INT16 sMapX, INT16 sMapY ) wcscat( wString, wSubString ); } - AdjustXForLeftMapEdge(wString, &sScreenX); - mprintf( ( sScreenX - StringPixLengthArg( MAP_FONT, wcslen(wString), wString ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MAP_FONT ), wString ); + AdjustXForLeftMapEdge(wString, &sScreenX, MapMineLabelsFont); + mprintf( ( sScreenX - StringPixLengthArg( MapMineLabelsFont, wcslen(wString), wString ) / 2 ) , sScreenY + ubLineCnt * GetFontHeight( MapMineLabelsFont ), wString ); ubLineCnt++; } @@ -5240,8 +5133,8 @@ void BlitMineText( INT16 sMapX, INT16 sMapY ) } - -void AdjustXForLeftMapEdge(STR16 wString, INT16 *psX) +// HEADROCK HAM 5: Now takes argument for font. +void AdjustXForLeftMapEdge(STR16 wString, INT16 *psX, INT32 iFont) { INT16 sStartingX, sPastEdge; @@ -5249,7 +5142,7 @@ void AdjustXForLeftMapEdge(STR16 wString, INT16 *psX) // it's ok to cut strings off in zoomed mode return; - sStartingX = *psX - (StringPixLengthArg( MAP_FONT, wcslen(wString), wString ) / 2); + sStartingX = *psX - (StringPixLengthArg( iFont, wcslen(wString), wString ) / 2); sPastEdge = (MAP_VIEW_START_X + 23) - sStartingX; if (sPastEdge > 0) @@ -6951,6 +6844,7 @@ UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) UINT32 uiSectorFlags = SectorInfo[ SECTOR( sSectorX, sSectorY ) ].uiFlags; BOOLEAN fDetection = FALSE; BOOLEAN fCount = FALSE; + BOOLEAN fDirection = FALSE; Assert(sSectorX > 0 && sSectorY > 0 && sSectorX < 17 && sSectorY < 17); @@ -7037,27 +6931,40 @@ UINT32 WhatPlayerKnowsAboutEnemiesInSector( INT16 sSectorX, INT16 sSectorY ) if (SectorInfo[ SECTOR( sSectorX, sSectorY ) ].ubDetectionLevel & 1) { fDetection = TRUE; + fDirection = TRUE; } if (SectorInfo[ SECTOR( sSectorX, sSectorY ) ].ubDetectionLevel & (1<<2) ) { fCount = TRUE; + fDirection = TRUE; } + // HEADROCK HAM 5: New cases below if (!fDetection) { // no information available return KNOWS_NOTHING; } - else if (!fCount) + else if (!fCount && !fDirection) { // No accurate information return KNOWS_THEYRE_THERE; } - else + else if (!fCount) + { + // No accurate information but have direction + return KNOWS_THEYRE_THERE_AND_WHERE_GOING; + } + else if (!fDirection) { // Accurate information return KNOWS_HOW_MANY; } + else + { + // Accurate information including direction of travel! + return KNOWS_HOW_MANY_AND_WHERE_GOING; + } } @@ -7154,6 +7061,7 @@ void HandleShowingOfEnemyForcesInSector( INT16 sSectorX, INT16 sSectorY, INT8 bS switch ( WhatPlayerKnowsAboutEnemiesInSector( sSectorX, sSectorY ) ) { + // HEADROCK HAM 5: New cases below for showing enemy group heading. case KNOWS_NOTHING: // display nothing break; @@ -7163,10 +7071,26 @@ void HandleShowingOfEnemyForcesInSector( INT16 sSectorX, INT16 sSectorY, INT8 bS ShowUncertainNumberEnemiesInSector( sSectorX, sSectorY ); break; + case KNOWS_THEYRE_THERE_AND_WHERE_GOING: + // display a question mark + ShowUncertainNumberEnemiesInSector( sSectorX, sSectorY ); + // display their direction of movement, if valid. + ShowEnemyGroupsInMotion( sSectorX, sSectorY ); + break; + case KNOWS_HOW_MANY: // display individual icons for each enemy, starting at the received icon position index ShowEnemiesInSector( sSectorX, sSectorY, sNumberOfEnemies, ubIconPosition ); break; + + // HEADROCK HAM 5: New case for showing enemy groups AND where the are headed. + case KNOWS_HOW_MANY_AND_WHERE_GOING: + // display individual icons for each enemy, starting at the received icon position index + ShowEnemiesInSector( sSectorX, sSectorY, sNumberOfEnemies, ubIconPosition ); + // display their direction of movement, if valid. + ShowEnemyGroupsInMotion( sSectorX, sSectorY ); + break; + } } @@ -7205,8 +7129,6 @@ void ShowSAMSitesOnStrategicMap( void ) INT16 sX = 0, sY = 0; HVOBJECT hHandle; INT8 ubVidObjIndex = 0; - UINT8 *pDestBuf2; - UINT32 uiDestPitchBYTES; CHAR16 wString[ 40 ]; @@ -7226,7 +7148,9 @@ void ShowSAMSitesOnStrategicMap( void ) // get the sector x and y sSectorX = gpSamSectorX[ iCounter ]; sSectorY = gpSamSectorY[ iCounter ]; - + + // HEADROCK HAM 5: Zoom flag no longer used. + /* if( fZoomFlag ) { pDestBuf2 = LockVideoSurface( guiSAVEBUFFER, &uiDestPitchBYTES ); @@ -7238,42 +7162,59 @@ void ShowSAMSitesOnStrategicMap( void ) sY -= 10; ubVidObjIndex = 0; } + else { + */ GetScreenXYFromMapXY( sSectorX, sSectorY, &sX, &sY ); sX += 5; sY += 3; ubVidObjIndex = 1; - } + //} + + INT16 sIconX = sX + 5; + INT16 sIconY = sY + 3; // draw SAM site icon GetVideoObject( &hHandle, guiSAMICON); - BltVideoObject( guiSAVEBUFFER, hHandle, ubVidObjIndex, sX, sY, VO_BLT_SRCTRANSPARENCY, NULL ); - + BltVideoObject( guiSAVEBUFFER, hHandle, ubVidObjIndex, sIconX, sIconY, VO_BLT_SRCTRANSPARENCY, NULL ); if( fShowAircraftFlag ) { // write "SAM Site" centered underneath - if( fZoomFlag ) + // HEADROCK HAM 5: Font size is now dynamic. + + INT32 MapSAMSiteFont; + if (iResolution <= _800x600 ) + { + MapSAMSiteFont = MAP_FONT; + } + else + { + MapSAMSiteFont = FONT14ARIAL; + } + + // HEADROCK HAM 5: Zoom flag no longer used. + /*if( fZoomFlag ) { sX += 9; sY += 19; } else - { - sX += 6; - sY += 16; - } + {*/ + INT16 sLabelX = sX + (MAP_GRID_X / 2); + INT16 sLabelY = sY + MAP_GRID_Y + 2; + //} wcscpy( wString, pLandTypeStrings[ SAM_SITE ] ); // we're CENTERING the first string AROUND sX, so calculate the starting X value - sX -= StringPixLength( wString, MAP_FONT) / 2; + sLabelX -= StringPixLength( wString, MapSAMSiteFont) / 2; // if within view region...render, else don't - if( ( sX > MAP_VIEW_START_X + MAP_VIEW_WIDTH ) || ( sX < MAP_VIEW_START_X ) || - ( sY > MAP_VIEW_START_Y + MAP_VIEW_HEIGHT ) || ( sY < MAP_VIEW_START_Y ) ) + if( ( sLabelX > MAP_VIEW_START_X + MAP_VIEW_WIDTH ) || ( sLabelX < MAP_VIEW_START_X ) || + ( sLabelY > MAP_VIEW_START_Y + MAP_VIEW_HEIGHT ) || ( sLabelY < MAP_VIEW_START_Y ) ) { continue; } @@ -7284,14 +7225,14 @@ void ShowSAMSitesOnStrategicMap( void ) // clip blits to mapscreen region ClipBlitsToMapViewRegion( ); - SetFont(MAP_FONT); + SetFont(MapSAMSiteFont); // Green on green doesn't contrast well, use Yellow SetFontForeground(FONT_MCOLOR_LTYELLOW); SetFontBackground(FONT_MCOLOR_BLACK); // draw the text - gprintfdirty( sX, sY, wString ); - mprintf( sX, sY, wString); + gprintfdirty( sLabelX, sLabelY, wString ); + mprintf( sLabelX, sLabelY, wString); // restore clip blits RestoreClipRegionToFullScreen( ); @@ -7433,7 +7374,18 @@ void ShowItemsOnMap( void ) SetFontDestBuffer( guiSAVEBUFFER, MapScreenRect.iLeft + 2, MapScreenRect.iTop, MapScreenRect.iRight, MapScreenRect.iBottom , FALSE ); - SetFont(MAP_FONT); + // HEADROCK HAM 5: Map Font now dynamic. + INT32 MapItemsFont; + if (iResolution <= _800x600 ) + { + MapItemsFont = MAP_FONT; + } + else + { + MapItemsFont = FONT14ARIAL; + } + + SetFont(MapItemsFont); SetFontForeground(FONT_MCOLOR_LTGREEN); SetFontBackground(FONT_MCOLOR_BLACK); @@ -7455,7 +7407,7 @@ void ShowItemsOnMap( void ) swprintf( sString, L"%d", uiItemCnt ); - FindFontCenterCoordinates( sXCorner, sYCorner, MAP_GRID_X, MAP_GRID_Y, sString, MAP_FONT, &usXPos, &usYPos ); + FindFontCenterCoordinates( sXCorner, sYCorner, MAP_GRID_X, MAP_GRID_Y, sString, MapItemsFont, &usXPos, &usYPos ); // sXPos -= StringPixLength( sString, MAP_FONT ) / 2; gprintfdirty( usXPos, usYPos, sString ); diff --git a/Strategic/Map Screen Interface Map.h b/Strategic/Map Screen Interface Map.h index dc874386..c94d735c 100644 --- a/Strategic/Map Screen Interface Map.h +++ b/Strategic/Map Screen Interface Map.h @@ -98,6 +98,7 @@ void RestoreClipRegionToFullScreenForRectangle( UINT32 uiDestPitchBYTES ); // show the icons for people in motion void ShowPeopleInMotion( INT16 sX, INT16 sY ); +void ShowEnemyGroupsInMotion( INT16 sX, INT16 sY); // last sector in helicopter's path INT16 GetLastSectorOfHelicoptersPath( void ); @@ -159,7 +160,9 @@ enum { enum { KNOWS_NOTHING = 0, KNOWS_THEYRE_THERE, + KNOWS_THEYRE_THERE_AND_WHERE_GOING, KNOWS_HOW_MANY, + KNOWS_HOW_MANY_AND_WHERE_GOING, }; @@ -273,7 +276,8 @@ extern UINT32 guiBULLSEYE; // character between sector icons extern UINT32 guiCHARBETWEENSECTORICONS; -extern UINT32 guiCHARBETWEENSECTORICONSCLOSE; +// HEADROCK HAM 5: Enemies Between Sectors icons +extern UINT32 guiENEMYBETWEENSECTORICONS; // the viewable map bound region extern SGPRect MapScreenRect; @@ -296,6 +300,9 @@ extern INT8 bSelectedContractChar; // map arrows graphical index value extern UINT32 guiMAPCURSORS; +// HEADROCK HAM 5: New pathing arrows may replace the above eventually, but for now a separate variable will do. +extern UINT32 guiMapPathingArrows; + // has temp path for character path or helicopter been already drawn extern BOOLEAN fTempPathAlreadyDrawn; diff --git a/Strategic/Map Screen Interface TownMine Info.cpp b/Strategic/Map Screen Interface TownMine Info.cpp index 3b6a96c6..2e49607a 100644 --- a/Strategic/Map Screen Interface TownMine Info.cpp +++ b/Strategic/Map Screen Interface TownMine Info.cpp @@ -59,6 +59,8 @@ UINT16 sTotalButtonWidth = 0; extern BOOLEAN fMapScreenBottomDirty; //extern UINT8 gubMonsterMineInfestation[]; +// HEADROCK HAM 5: Must be externed here for correct sector name display +extern CHAR16 gzSectorNames[256][4][MAX_SECTOR_NAME_LENGTH]; // create the town/mine info box void CreateTownInfoBox( void ); @@ -294,43 +296,96 @@ void AddTextToTownBox( void ) usTownSectorIndex = SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ); - switch( usTownSectorIndex ) + AssertGE(usTownSectorIndex, 0); + AssertLT(usTownSectorIndex,256); + + //////////////////////////////////// + // HEADROCK HAM 5: + // Read and verify XML sector names + BOOLEAN fSectorHasXMLNames = TRUE; + CHAR16 zUnexplored[MAX_SECTOR_NAME_LENGTH]; + CHAR16 zExplored[MAX_SECTOR_NAME_LENGTH]; + + wcscpy( zUnexplored, gzSectorNames[ usTownSectorIndex ][0] ); + wcscpy( zExplored, gzSectorNames[ usTownSectorIndex ][2] ); + + if (zUnexplored[0] == 0 || zExplored[0] == 0) { - case SEC_B13: - AddMonoString( &hStringHandle, pLandTypeStrings[ DRASSEN_AIRPORT_SITE ] ); - break; - case SEC_F8: - AddMonoString( &hStringHandle, pLandTypeStrings[ CAMBRIA_HOSPITAL_SITE ] ); - break; - case SEC_J9: //Tixa - //if( !fFoundTixa ) - if( gfHiddenTown[ TIXA ] == FALSE ) - AddMonoString( &hStringHandle, pLandTypeStrings[ SAND ] ); - else - AddMonoString( &hStringHandle, pTownNames[ TIXA ] ); - break; - case SEC_K4: //Orta - //if( !fFoundOrta ) - if( gfHiddenTown[ ORTA ] == FALSE ) - AddMonoString( &hStringHandle, pLandTypeStrings[ SWAMP ] ); - else - AddMonoString( &hStringHandle, pTownNames[ ORTA ] ); - break; - case SEC_N3: - AddMonoString( &hStringHandle, pLandTypeStrings[ MEDUNA_AIRPORT_SITE ] ); - break; - default: - if( usTownSectorIndex == SEC_N4 && fSamSiteFound[ SAM_SITE_FOUR ] ) - { //Meduna's SAM site - AddMonoString( &hStringHandle, pLandTypeStrings[ MEDUNA_SAM_SITE ] ); - } - else - { // town name - swprintf( wString, L"%s", pTownNames[ ubTownId ] ); - AddMonoString( &hStringHandle, wString ); - } - break; + fSectorHasXMLNames = FALSE; } + + if (fSectorHasXMLNames) // ABOVE GROUND XML + { + // HEADROCK HAM 3.6: The program can now read custom names from XML for all above-ground sectors. + // In the event that a specific name or set of names is missing, the program generates a default + // name as it always has. + // I've also updated the SAM Site sectors to rely on SamSite.XML data. + + BOOLEAN fVisited = (SectorInfo[ usTownSectorIndex ].uiFlags & SF_ALREADY_VISITED); + BOOLEAN fSAMSiteKnown = FALSE; + + // Test for known SAM Site at this location + for (UINT16 x=0; x < MAX_NUMBER_OF_SAMS; x++) + { + if ( pSamList[x] == usTownSectorIndex ) + { + if ( fSamSiteFound[ x ] ) + { + fSAMSiteKnown = TRUE; + } + } + } + + if (fVisited || fSAMSiteKnown) + { + AddMonoString( &hStringHandle, zExplored ); + } + else + { + AddMonoString( &hStringHandle, zUnexplored ); + } + } + else // ABOVE GROUND HARDCODED + { + switch( usTownSectorIndex ) + { + case SEC_B13: + AddMonoString( &hStringHandle, pLandTypeStrings[ DRASSEN_AIRPORT_SITE ] ); + break; + case SEC_F8: + AddMonoString( &hStringHandle, pLandTypeStrings[ CAMBRIA_HOSPITAL_SITE ] ); + break; + case SEC_J9: //Tixa + //if( !fFoundTixa ) + if( gfHiddenTown[ TIXA ] == FALSE ) + AddMonoString( &hStringHandle, pLandTypeStrings[ SAND ] ); + else + AddMonoString( &hStringHandle, pTownNames[ TIXA ] ); + break; + case SEC_K4: //Orta + //if( !fFoundOrta ) + if( gfHiddenTown[ ORTA ] == FALSE ) + AddMonoString( &hStringHandle, pLandTypeStrings[ SWAMP ] ); + else + AddMonoString( &hStringHandle, pTownNames[ ORTA ] ); + break; + case SEC_N3: + AddMonoString( &hStringHandle, pLandTypeStrings[ MEDUNA_AIRPORT_SITE ] ); + break; + default: + if( usTownSectorIndex == SEC_N4 && fSamSiteFound[ SAM_SITE_FOUR ] ) + { //Meduna's SAM site + AddMonoString( &hStringHandle, pLandTypeStrings[ MEDUNA_SAM_SITE ] ); + } + else + { // town name + swprintf( wString, L"%s", pTownNames[ ubTownId ] ); + AddMonoString( &hStringHandle, wString ); + } + break; + } + } + // blank line AddMonoString( &hStringHandle, L"" ); @@ -523,31 +578,83 @@ void AddTextToBlankSectorBox( void ) // get the sector value usSectorValue = SECTOR( bCurrentTownMineSectorX, bCurrentTownMineSectorY ); - switch( usSectorValue ) - { - case SEC_D2: //Chitzena SAM - if( !fSamSiteFound[ SAM_SITE_ONE ] ) - AddMonoString( &hStringHandle, pLandTypeStrings[ TROPICS ] ); - else - AddMonoString( &hStringHandle, pLandTypeStrings[ TROPICS_SAM_SITE ] ); - break; - case SEC_D15: //Drassen SAM - if( !fSamSiteFound[ SAM_SITE_TWO ] ) - AddMonoString( &hStringHandle, pLandTypeStrings[ SPARSE ] ); - else - AddMonoString( &hStringHandle, pLandTypeStrings[ SPARSE_SAM_SITE ] ); - break; - case SEC_I8: //Cambria SAM - if( !fSamSiteFound[ SAM_SITE_THREE ] ) - AddMonoString( &hStringHandle, pLandTypeStrings[ SAND ] ); - else - AddMonoString( &hStringHandle, pLandTypeStrings[ SAND_SAM_SITE ] ); - break; - // SAM Site 4 in Meduna is within town limits, so it's handled in AddTextToTownBox() + AssertGE(usSectorValue, 0); + AssertLT(usSectorValue,256); - default: - AddMonoString( &hStringHandle, pLandTypeStrings[ ( SectorInfo[ usSectorValue ].ubTraversability[ 4 ] ) ] ); - break; + //////////////////////////////////// + // HEADROCK HAM 5: + // Read and verify XML sector names + BOOLEAN fSectorHasXMLNames = TRUE; + CHAR16 zUnexplored[MAX_SECTOR_NAME_LENGTH]; + CHAR16 zExplored[MAX_SECTOR_NAME_LENGTH]; + + wcscpy( zUnexplored, gzSectorNames[ usSectorValue ][0] ); + wcscpy( zExplored, gzSectorNames[ usSectorValue ][2] ); + + if (zUnexplored[0] == 0 || zExplored[0] == 0) + { + fSectorHasXMLNames = FALSE; + } + + if (fSectorHasXMLNames) // ABOVE GROUND XML + { + // HEADROCK HAM 3.6: The program can now read custom names from XML for all above-ground sectors. + // In the event that a specific name or set of names is missing, the program generates a default + // name as it always has. + // I've also updated the SAM Site sectors to rely on SamSite.XML data. + + BOOLEAN fVisited = (SectorInfo[ usSectorValue ].uiFlags & SF_ALREADY_VISITED); + BOOLEAN fSAMSiteKnown = FALSE; + + // Test for known SAM Site at this location + for (UINT16 x=0; x < MAX_NUMBER_OF_SAMS; x++) + { + if ( pSamList[x] == usSectorValue ) + { + if ( fSamSiteFound[ x ] ) + { + fSAMSiteKnown = TRUE; + } + } + } + + if (fVisited || fSAMSiteKnown) + { + AddMonoString( &hStringHandle, zExplored ); + } + else + { + AddMonoString( &hStringHandle, zUnexplored ); + } + } + else // ABOVE GROUND HARDCODED + { + switch( usSectorValue ) + { + case SEC_D2: //Chitzena SAM + if( !fSamSiteFound[ SAM_SITE_ONE ] ) + AddMonoString( &hStringHandle, pLandTypeStrings[ TROPICS ] ); + else + AddMonoString( &hStringHandle, pLandTypeStrings[ TROPICS_SAM_SITE ] ); + break; + case SEC_D15: //Drassen SAM + if( !fSamSiteFound[ SAM_SITE_TWO ] ) + AddMonoString( &hStringHandle, pLandTypeStrings[ SPARSE ] ); + else + AddMonoString( &hStringHandle, pLandTypeStrings[ SPARSE_SAM_SITE ] ); + break; + case SEC_I8: //Cambria SAM + if( !fSamSiteFound[ SAM_SITE_THREE ] ) + AddMonoString( &hStringHandle, pLandTypeStrings[ SAND ] ); + else + AddMonoString( &hStringHandle, pLandTypeStrings[ SAND_SAM_SITE ] ); + break; + // SAM Site 4 in Meduna is within town limits, so it's handled in AddTextToTownBox() + + default: + AddMonoString( &hStringHandle, pLandTypeStrings[ ( SectorInfo[ usSectorValue ].ubTraversability[ 4 ] ) ] ); + break; + } } // blank line @@ -723,7 +830,9 @@ void AddCommonInfoToBox(void) wcscpy(wString, pwMiscSectorStrings[ 3 ] ); break; + // HEADROCK HAM 5: New Case case KNOWS_THEYRE_THERE: + case KNOWS_THEYRE_THERE_AND_WHERE_GOING: // if there are any there if ( ubNumEnemies > 0 ) { @@ -737,7 +846,9 @@ void AddCommonInfoToBox(void) } break; + // HEADROCK HAM 5: New case case KNOWS_HOW_MANY: + case KNOWS_HOW_MANY_AND_WHERE_GOING: // show exactly how many if (numEnemiesOnMap != ubNumEnemies) swprintf( wString, L"%d (%d)", numEnemiesOnMap, ubNumEnemies ); diff --git a/Strategic/MilitiaSquads.cpp b/Strategic/MilitiaSquads.cpp index 111ebdde..3e615e0e 100644 --- a/Strategic/MilitiaSquads.cpp +++ b/Strategic/MilitiaSquads.cpp @@ -71,7 +71,8 @@ UINT8 gubManualRestrictMilitia[ 256 ]; // HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors BOOLEAN gDynamicRestrictMilitia[ 256 ]; // HEADROCK HAM B1: Function that dynamically unrestricts sectors as we take over towns. -extern void AdjustRoamingRestrictions(); +// HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. +extern void AdjustRoamingRestrictions( BOOLEAN fRecheck ); DYNAMICRESTRICTIONS gDynamicRestrictions[5001]; @@ -2167,7 +2168,8 @@ void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ) } }*/ -void AdjustRoamingRestrictions() +// HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. +void AdjustRoamingRestrictions( BOOLEAN fRecheck ) { UINT32 uiCapturedTownsFlag = 0; UINT16 cnt = 0; @@ -2203,7 +2205,19 @@ void AdjustRoamingRestrictions() } } } -} + + // HEADROCK HAM 5: All restricted sectors are checked to see they aren't manually-permitted. + if (fRecheck) + { + for (cnt = 0; cnt < 256; cnt++) + { + if (gDynamicRestrictMilitia[cnt] == FALSE) + { + gubManualRestrictMilitia[cnt] = MANUAL_MOBILE_RESTRICTED; + } + } + } +} // HEADROCK HAM B2.7: This is a copy of an existing function that generates possible movement directions for militia. diff --git a/Strategic/MilitiaSquads.h b/Strategic/MilitiaSquads.h index f2531be9..d7af55ae 100644 --- a/Strategic/MilitiaSquads.h +++ b/Strategic/MilitiaSquads.h @@ -25,7 +25,8 @@ BOOLEAN MoveOneBestMilitiaMan(INT16 sMapX, INT16 sMapY, INT16 sTMapX, INT16 sTMa void MilitiaFollowPlayer( INT16 sMapX, INT16 sMapY, INT16 sDMapX, INT16 sDMapY ); // HEADROCK HAM B1: Changes the allowed militia sectors -extern void AdjustRoamingRestrictions(); +// HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. +extern void AdjustRoamingRestrictions( BOOLEAN fRecheck ); // HEADROCK HAM 4: Yet ANOTHER array, this one holds player-set restrictions. It interacts with the dynamic one below. extern UINT8 gubManualRestrictMilitia[ 256 ]; // HEADROCK HAM B1: Alternate array keeps track of dynamically unrestricted sectors diff --git a/Strategic/PreBattle Interface.cpp b/Strategic/PreBattle Interface.cpp index 9db1d2e8..f01eabc6 100644 --- a/Strategic/PreBattle Interface.cpp +++ b/Strategic/PreBattle Interface.cpp @@ -656,7 +656,9 @@ void InitPreBattleInterface( GROUP *pBattleGroup, BOOLEAN fPersistantPBI ) // adjust the chance for what we know about the sector if( WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) == KNOWS_NOTHING ) iChance += 20; - else if( WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) == KNOWS_THEYRE_THERE ) + // HEADROCK HAM 5: Added new possible value... + else if( WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) == KNOWS_THEYRE_THERE || + WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) == KNOWS_THEYRE_THERE_AND_WHERE_GOING ) iChance += 5; //if( GetSectorFlagStatus( gubPBSectorX, gubPBSectorY, 0, SF_ALREADY_VISITED ) == TRUE ) // iChance -= 10; // if we already visited this sector @@ -1246,7 +1248,7 @@ void RenderPreBattleInterface() if( gubEnemyEncounterCode == CREATURE_ATTACK_CODE || gubEnemyEncounterCode == BLOODCAT_AMBUSH_CODE || gubEnemyEncounterCode == ENTERING_BLOODCAT_LAIR_CODE || - WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) != KNOWS_HOW_MANY ) + WhatPlayerKnowsAboutEnemiesInSector( gubPBSectorX, gubPBSectorY ) < KNOWS_HOW_MANY ) // HEADROCK HAM 5: New case above this one... { // don't know how many swprintf( str, L"?" ); diff --git a/Strategic/Strategic Merc Handler.cpp b/Strategic/Strategic Merc Handler.cpp index 4280e892..0cbb17a0 100644 --- a/Strategic/Strategic Merc Handler.cpp +++ b/Strategic/Strategic Merc Handler.cpp @@ -587,7 +587,8 @@ void MercDailyUpdate() // HEADROCK HAM B1: Run a function to redefine Roaming Militia Restrictions. if (gGameExternalOptions.fDynamicRestrictRoaming) { - AdjustRoamingRestrictions(); + // HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. + AdjustRoamingRestrictions( FALSE ); } // HEADROCK HAM 3.6: Pay debt for operating Facilities today. If can't be paid, apply loyalty hit. diff --git a/Strategic/Strategic Movement.cpp b/Strategic/Strategic Movement.cpp index f4d89154..6473965a 100644 --- a/Strategic/Strategic Movement.cpp +++ b/Strategic/Strategic Movement.cpp @@ -69,7 +69,8 @@ class SOLDIERTYPE; extern UINT32 guiLastTacticalRealTime; // the delay for a group about to arrive -#define ABOUT_TO_ARRIVE_DELAY 5 +// HEADROCK HAM 5: 5 minutes? Make it 30. 5 minutes tend to pass in an instant while unpaused... +#define ABOUT_TO_ARRIVE_DELAY 30 GROUP *gpGroupList; diff --git a/Strategic/Strategic Town Loyalty.cpp b/Strategic/Strategic Town Loyalty.cpp index 193e35d8..73e2ff25 100644 --- a/Strategic/Strategic Town Loyalty.cpp +++ b/Strategic/Strategic Town Loyalty.cpp @@ -1859,7 +1859,8 @@ void CheckIfEntireTownHasBeenLiberated( INT8 bTownId, INT16 sSectorX, INT16 sSec // HEADROCK HAM B1: Run a function to redefine Roaming Militia Restrictions on city capture. if (gGameExternalOptions.fDynamicRestrictRoaming) { - AdjustRoamingRestrictions(); + // HEADROCK HAM 5: New flag tells us to also recheck restriced sectors. + AdjustRoamingRestrictions( FALSE ); } // set flag even for towns where you can't train militia, useful for knowing Orta/Tixa were previously controlled diff --git a/Strategic/Strategic_VS2010.vcxproj b/Strategic/Strategic_VS2010.vcxproj index 62d4ffb8..0f1aa230 100644 --- a/Strategic/Strategic_VS2010.vcxproj +++ b/Strategic/Strategic_VS2010.vcxproj @@ -126,6 +126,7 @@ + diff --git a/Strategic/Strategic_VS2010.vcxproj.filters b/Strategic/Strategic_VS2010.vcxproj.filters index aece6dcc..3f8afb2e 100644 --- a/Strategic/Strategic_VS2010.vcxproj.filters +++ b/Strategic/Strategic_VS2010.vcxproj.filters @@ -335,5 +335,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Strategic/XML_CoolnessBySector.cpp b/Strategic/XML_CoolnessBySector.cpp new file mode 100644 index 00000000..6ac239f4 --- /dev/null +++ b/Strategic/XML_CoolnessBySector.cpp @@ -0,0 +1,169 @@ +#ifdef PRECOMPILEDHEADERS + #include "Strategic All.h" + #include "XML.h" +#else + #include "builddefines.h" + #include + #include "XML.h" + #include "expat.h" + #include "string.h" + #include "Campaign Types.h" + #include "FileMan.h" + #include "MemMan.h" + #include "Debug Control.h" + #include "mapscreen.h" + + #include "Soldier Create.cpp" +#endif + +#define MAX_CHAR_DATA_LENGTH 500 + +extern UINT32 gCoolnessBySector[256]; + +typedef struct +{ + PARSE_STAGE curElement; + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + INT32 iCurCoolness; + UINT32 uiRowNumber; + UINT32 currentDepth; + UINT32 maxReadDepth; +} SectorCoolnessParseData; + + +static void XMLCALL +SectorCoolnessStartElementHandle(void *userData, const XML_Char *name, const char **atts) +{ + SectorCoolnessParseData * pData = (SectorCoolnessParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + + if(strcmp(name, "COOLNESS_BY_SECTOR") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = ELEMENT_LIST; + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "MAP_ROW") == 0 && pData->curElement == ELEMENT_LIST) + { + UINT32 uiAttrIndex; + pData->curElement = ELEMENT; + + for(uiAttrIndex = 0;atts[uiAttrIndex] != NULL;uiAttrIndex += 2) + if(strcmp(atts[uiAttrIndex], "row") == 0) + { + pData->uiRowNumber = atol(atts[uiAttrIndex+1]); + break; + } + + if(atts[uiAttrIndex] != NULL && pData->uiRowNumber < MAP_WORLD_Y) + pData->maxReadDepth++; //we are not skipping this element + } + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + + + +static void XMLCALL +SectorCoolnessCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + SectorCoolnessParseData * pData = (SectorCoolnessParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth && strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); +} + + +static void XMLCALL +SectorCoolnessEndElementHandle(void *userData, const XML_Char *name) +{ + SectorCoolnessParseData * pData = (SectorCoolnessParseData *) userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "COOLNESS_BY_SECTOR") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT_NONE; + } + else if(strcmp(name, "MAP_ROW") == 0 && pData->curElement == ELEMENT) + { + STR8 curBuffer = pData->szCharData + strspn(pData->szCharData," \t\n\r"); + UINT32 curCellIndex = 0; + UINT32 curNumber; + + pData->curElement = ELEMENT_LIST; + + while(curBuffer[0] != '\0') + { + sscanf( curBuffer,"%d",&curNumber); + + gCoolnessBySector[SECTOR(curCellIndex+1, pData->uiRowNumber) ] = curNumber; + + curCellIndex++; + curBuffer += strcspn(curBuffer," \t\n\r\0"); + curBuffer += strspn(curBuffer," \t\n\r"); + } + } + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + +BOOLEAN ReadInCoolnessBySector(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + SectorCoolnessParseData pData; + + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, SectorCoolnessStartElementHandle, SectorCoolnessEndElementHandle); + XML_SetCharacterDataHandler(parser, SectorCoolnessCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + XML_SetUserData(parser, &pData); + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + sprintf(errorBuf, "XML Parser Error in CoolnessBySector.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + XML_ParserFree(parser); + + return TRUE; +} diff --git a/Strategic/mapscreen.cpp b/Strategic/mapscreen.cpp index 9fd7fe5e..6602262e 100644 --- a/Strategic/mapscreen.cpp +++ b/Strategic/mapscreen.cpp @@ -1016,7 +1016,7 @@ void MapscreenMarkButtonsDirty(); extern BOOLEAN CanRedistributeMilitiaInSector( INT16 sClickedSectorX, INT16 sClickedSectorY, INT8 bClickedTownId ); extern INT32 GetNumberOfMercsInUpdateList( void ); -extern INT32 SellItem( OBJECTTYPE& object, BOOLEAN useModifier = TRUE ); +extern INT32 SellItem( OBJECTTYPE& object, BOOLEAN fAll, BOOLEAN useModifier = TRUE ); void DeleteAllItemsInInventoryPool(); #ifdef JA2UB @@ -1066,18 +1066,21 @@ void BeginSellAllCallBack( UINT8 bExitValue ) fMapPanelDirty = TRUE; gpItemPointer = &gItemPointer; // Sell Item - iPrice += SellItem( gItemPointer ); + // HEADROCK HAM 5: Added argument + iPrice += SellItem( gItemPointer, TRUE ); gpItemPointer = NULL; fMapInventoryItem = FALSE; - if (iPrice == 0) { - iPrice = 1; - } + // HEADROCK HAM 5: Sale of $0 now re-enabled. + //if (iPrice == 0) { + // iPrice = 1; + //} anythingSold = true; HandleButtonStatesWhileMapInventoryActive(); } } } - if(anythingSold == true) + // HEADROCK HAM 5: No transaction if no money. + if(anythingSold == true && iPrice > 0) AddTransactionToPlayersBook( SOLD_ITEMS, 0, GetWorldTotalMin(), iPrice ); } } @@ -4761,6 +4764,18 @@ UINT32 MapScreenHandle(void) FilenameForBPP("INTERFACE\\mapcursr.sti", VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &guiMAPCURSORS)); + // HEADROCK HAM 5: New pathing arrows may replace the above eventually, but for now a separate variable will do. + VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; + + if (iResolution == 0) + FilenameForBPP("INTERFACE\\map_pathing_arrows_640.sti", VObjectDesc.ImageFile); + else if (iResolution == 1) + FilenameForBPP("INTERFACE\\map_pathing_arrows_800.sti", VObjectDesc.ImageFile); + else if (iResolution == 2) + FilenameForBPP("INTERFACE\\map_pathing_arrows_1024.sti", VObjectDesc.ImageFile); + + CHECKF(AddVideoObject(&VObjectDesc, &guiMapPathingArrows)); + VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP("INTERFACE\\Mine_1.sti", VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &guiSubLevel1)); @@ -4908,20 +4923,42 @@ UINT32 MapScreenHandle(void) } VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; - FilenameForBPP("INTERFACE\\merc_between_sector_icons.sti", VObjectDesc.ImageFile); + // HEADROCK HAM 5.4: Larger icons for merc movement + if (iResolution >= _640x480 && iResolution < _800x600) + FilenameForBPP("INTERFACE\\merc_between_sector_icons_640.sti", VObjectDesc.ImageFile); + else if (iResolution < _1024x768) + FilenameForBPP("INTERFACE\\merc_between_sector_icons_800.sti", VObjectDesc.ImageFile); + else + FilenameForBPP("INTERFACE\\merc_between_sector_icons_1024.sti", VObjectDesc.ImageFile); + CHECKF(AddVideoObject(&VObjectDesc, &guiCHARBETWEENSECTORICONS)); + // HEADROCK HAM 5.1: Enemies between sectors VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; - FilenameForBPP("INTERFACE\\merc_mvt_green_arrows.sti", VObjectDesc.ImageFile); - CHECKF(AddVideoObject(&VObjectDesc, &guiCHARBETWEENSECTORICONSCLOSE)); + if (iResolution >= _640x480 && iResolution < _800x600) + FilenameForBPP("INTERFACE\\enemy_between_sector_icons_640.sti", VObjectDesc.ImageFile); + else if (iResolution < _1024x768) + FilenameForBPP("INTERFACE\\enemy_between_sector_icons_800.sti", VObjectDesc.ImageFile); + else + FilenameForBPP("INTERFACE\\enemy_between_sector_icons_1024.sti", VObjectDesc.ImageFile); + + CHECKF(AddVideoObject(&VObjectDesc, &guiENEMYBETWEENSECTORICONS)); VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; FilenameForBPP("INTERFACE\\GreenArr.sti", VObjectDesc.ImageFile); CHECKF(AddVideoObject(&VObjectDesc, &guiLEVELMARKER)); VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; - FilenameForBPP("INTERFACE\\Helicop.sti", VObjectDesc.ImageFile); + + // HEADROCK HAM 5: Resolution-dependent icon + if (iResolution >= _640x480 && iResolution < _800x600) + FilenameForBPP("INTERFACE\\Helicopter_Map_Icon_640.sti", VObjectDesc.ImageFile); + else if (iResolution < _1024x768) + FilenameForBPP("INTERFACE\\Helicopter_Map_Icon_800.sti", VObjectDesc.ImageFile); + else + FilenameForBPP("INTERFACE\\Helicopter_Map_Icon_1024.sti", VObjectDesc.ImageFile); + CHECKF(AddVideoObject(&VObjectDesc, &guiHelicopterIcon)); VObjectDesc.fCreateFlags=VOBJECT_CREATE_FROMFILE; @@ -4933,7 +4970,6 @@ UINT32 MapScreenHandle(void) CHECKF(AddVideoObject(&VObjectDesc, &guiMapBorderHeliSectors)); - VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; FilenameForBPP("INTERFACE\\secondary_gun_hidden.sti", VObjectDesc.ImageFile); CHECKF( AddVideoObject( &VObjectDesc, &guiSecItemHiddenVO ) ); @@ -5602,14 +5638,23 @@ UINT32 MapScreenHandle(void) // is there a description to be displayed? // BOB : if we're displaying the attachment popup, don't redraw the IDB if (giActiveAttachmentPopup > -1) + { gItemDescAttachmentPopups[giActiveAttachmentPopup]->show(); + } + // HEADROCK HAM 5: So... we need a separate case for each popup that we have? That's not too bright. + else if (gfItemDescTransformPopupVisible) + { + gItemDescTransformPopup->show(); + } else - RenderItemDescriptionBox( ); + { + RenderItemDescriptionBox( ); + } // render clock // WANNE: Renders the clock in the strategy screen RenderClock(CLOCK_X, CLOCK_Y); - + #ifdef JA2TESTVERSION if( !gfWorldLoaded ) { @@ -8195,9 +8240,38 @@ void GetMapKeyboardInput( UINT32 *puiNewEvent ) break; } } + } + + // HEADROCK HAM 5: Alt alone shows sale prices on items in the sector inventory. + if (fShowMapInventoryPool) + { + if (fAlt) + { + if ( _KeyDown( SHIFT ) ) + { + if (gubRenderedMapInventorySalePrices < 2) + { + gubRenderedMapInventorySalePrices = 2; + fMapPanelDirty = TRUE; + } + } + else + { + if (gubRenderedMapInventorySalePrices == 0 || + gubRenderedMapInventorySalePrices == 2 ) + { + gubRenderedMapInventorySalePrices = 1; + fMapPanelDirty = TRUE; + } + } + } else { - HandleDefaultEvent(&InputEvent); + if (gubRenderedMapInventorySalePrices) + { + gubRenderedMapInventorySalePrices = 0; + fMapPanelDirty = TRUE; + } } } } @@ -8360,6 +8434,8 @@ INT32 iCounter2 = 0; DeleteVideoObjectFromIndex(guiSubLevel3); DeleteVideoObjectFromIndex( guiSleepIcon ); DeleteVideoObjectFromIndex(guiMAPCURSORS); + // HEADROCK HAM 5: New pathing arrows may replace the above eventually, but for now a separate variable will do. + DeleteVideoObjectFromIndex(guiMapPathingArrows); //DeleteVideoObjectFromIndex(guiMAPBORDER); DeleteVideoObjectFromIndex(guiCHARLIST); //DeleteVideoObjectFromIndex(guiCORNERADDONS); @@ -8377,7 +8453,8 @@ INT32 iCounter2 = 0; DeleteVideoObjectFromIndex(guiORTAICON); DeleteVideoObjectFromIndex(guiTIXAICON); DeleteVideoObjectFromIndex( guiCHARBETWEENSECTORICONS ); - DeleteVideoObjectFromIndex( guiCHARBETWEENSECTORICONSCLOSE ); + // HEADROCK HAM 5.1: Enemies between sectors arrows + DeleteVideoObjectFromIndex( guiENEMYBETWEENSECTORICONS ); DeleteVideoObjectFromIndex( guiLEVELMARKER ); DeleteVideoObjectFromIndex( guiMapBorderEtaPopUp ); @@ -8866,7 +8943,9 @@ void PollRightButtonInMapView( UINT32 *puiNewEvent ) // HEADROCK HAM 4: Toggle Militia Restrictions manually. This occurs when the Mobile Restrictions // view is turned on. Each click advances the sector's classification by 1. If max is reached, // loops back to start. - if ( fShowMobileRestrictionsFlag == TRUE ) + // HEADROCK HAM 5: Whoops, disallow changing restrictions for sectors that are + // not accessible to militia to begin with. + if ( fShowMobileRestrictionsFlag == TRUE && gDynamicRestrictMilitia[ SECTOR( sMapX, sMapY ) ] == TRUE) { // Restrict more. if ( gubManualRestrictMilitia[ SECTOR( sMapX, sMapY ) ] > MANUAL_MOBILE_NO_ENTER ) @@ -9343,9 +9422,15 @@ void MAPInvClickCallback( MOUSE_REGION *pRegion, INT32 iReason ) // If we do not have an item in hand, start moving it if ( gpItemPointer == NULL ) { - // Return if empty + if ( pSoldier->inv[ uiHandPos ].exists() == false ) { + if ( _KeyDown(CTRL) ){ + PocketPopupFull( pSoldier, uiHandPos ); // if ctrl is pressed, display all options + } else { + PocketPopupDefault( pSoldier, uiHandPos ); // display pocket-specific options + } + return; } @@ -9446,7 +9531,17 @@ void MAPInvClickCallback( MOUSE_REGION *pRegion, INT32 iReason ) // it's an attempt to attach; bring up the inventory panel if ( !InItemDescriptionBox( ) ) { - MAPInternalInitItemDescriptionBox( &(pSoldier->inv[ uiHandPos ]), 0, pSoldier ); + // HEADROCK HAM 5: Sector Inventory Item Desc Box no longer accessible during combat. + + if( gTacticalStatus.uiFlags & INCOMBAT ) + { + DoScreenIndependantMessageBox( New113HAMMessage[ 23 ], MSG_BOX_FLAG_OK, NULL ); + return; + } + else + { + MAPInternalInitItemDescriptionBox( &(pSoldier->inv[ uiHandPos ]), 0, pSoldier ); + } } return; } @@ -9456,8 +9551,18 @@ void MAPInvClickCallback( MOUSE_REGION *pRegion, INT32 iReason ) // TOO PAINFUL TO DO!! --CC if ( !InItemDescriptionBox( ) ) { - bJustOpenedItemDescPanel = true; // OJW - 20090319 - fix merging on mapscreen - see top of function - MAPInternalInitItemDescriptionBox( &(pSoldier->inv[ uiHandPos ]), 0, pSoldier ); + // HEADROCK HAM 5: Sector Inventory Item Desc Box no longer accessible during combat. + + if( gTacticalStatus.uiFlags & INCOMBAT ) + { + DoScreenIndependantMessageBox( New113HAMMessage[ 24 ], MSG_BOX_FLAG_OK, NULL ); + return; + } + else + { + bJustOpenedItemDescPanel = true; // OJW - 20090319 - fix merging on mapscreen - see top of function + MAPInternalInitItemDescriptionBox( &(pSoldier->inv[ uiHandPos ]), 0, pSoldier ); + } } /* @@ -13046,6 +13151,8 @@ INT32 iCounter2 = 0; { DeleteMapBottomGraphics( ); DeleteVideoObjectFromIndex( guiMAPCURSORS ); + // HEADROCK HAM 5: New pathing arrows may replace the above eventually, but for now a separate variable will do. + DeleteVideoObjectFromIndex(guiMapPathingArrows); DeleteVideoObjectFromIndex( guiSleepIcon ); //DeleteVideoObjectFromIndex(guiMAPBORDER); @@ -13069,7 +13176,8 @@ INT32 iCounter2 = 0; DeleteVideoObjectFromIndex(guiORTAICON); DeleteVideoObjectFromIndex(guiTIXAICON); DeleteVideoObjectFromIndex( guiCHARBETWEENSECTORICONS ); - DeleteVideoObjectFromIndex( guiCHARBETWEENSECTORICONSCLOSE ); + // HEADROCK HAM 5.1: Enemies between sectors arrows + DeleteVideoObjectFromIndex( guiENEMYBETWEENSECTORICONS ); DeleteVideoObjectFromIndex( guiLEVELMARKER ); DeleteVideoObjectFromIndex( guiMapBorderEtaPopUp ); DeleteVideoObjectFromIndex( guiSecItemHiddenVO ); diff --git a/Tactical/Animation Control.cpp b/Tactical/Animation Control.cpp index 2d0c27d8..f8f9123e 100644 --- a/Tactical/Animation Control.cpp +++ b/Tactical/Animation Control.cpp @@ -50,17 +50,17 @@ static UINT16 CrouchedShootRocketScript[MAX_FRAMES_PER_ANIM] = {757,1,2,3,4,5,6,7,8,9,10,11,12,470,430,13,14,15,16,477,17,18,19,20,753,499,999,0,0,0,0}; static UINT16 SwatWithKnifeScript[MAX_FRAMES_PER_ANIM] = //{1,2,3,4,5,6,7,703,8,9,10,11,704,12,13,14,15,16,17,18,19,501,999,0,0,0,0}; -{5,703,6,7,8,9,10,11,704,12,13,14,15,16,501,999,0,0,0,0};//òàêàÿ ïîñëåäîâàòåëüíîñòü ïðåäëàãàåòñÿ â äæà2áèí +{5,703,6,7,8,9,10,11,704,12,13,14,15,16,501,999,0,0,0,0};//such a sequence is available in dzha2bin static UINT16 SwatBackWithKnifeScript[MAX_FRAMES_PER_ANIM] = {16,15,14,13,12,704,11,10,9,8,7,6,703,5,501,999,0,0,0,0}; static UINT16 SwatBackWithNothingScript[MAX_FRAMES_PER_ANIM] = {16,15,14,13,12,704,11,10,9,8,7,6,703,5,501,999,0,0,0,0}; -//õç, ìîæåò ïîíàäîáèòñÿ ïðàâèòü öèôðû. ñêðèïò êèäàíèÿ ãðàíàò +//xs, may need to edit the numbers. throwing grenades script static UINT16 ThrowGrenadeStanceAnimationScript[MAX_FRAMES_PER_ANIM] = { 1,2,3,4,5,6,7,8,9,10,460,11,12,13,14,15,16,17,18,19,442,601,999,0,0,0,0 }; static UINT16 LobGrenadeStanceAnimationScript[MAX_FRAMES_PER_ANIM] = { 1,2,3,4,5,6,7,8,9,10,460,11,12,13,14,15,16,17,18,19,442,601,999,0,0,0,0 }; -//ïåðåêàò +//Rolls static UINT16 ROLL_L_AnimationScript[MAX_FRAMES_PER_ANIM] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,501,999,0,0,0,0 }; static UINT16 ROLL_R_AnimationScript[MAX_FRAMES_PER_ANIM] = { 14,13,12,11,10,9,8,7,6,5,4,3,2,1,501,999,0,0,0,0 }; @@ -1781,10 +1781,10 @@ void InitAnimationSurfacesPerBodytype( ) gubAnimSurfaceIndex[ BIGMALE ][ SWATTING_WK ] = BGMSWKNIFE; gubAnimSurfaceIndex[ BIGMALE ][ SWAT_BACKWARDS_WK ] = BGMSWKNIFE; gubAnimSurfaceIndex[ BIGMALE ][ SWAT_BACKWARDS_NOTHING ] = BGMNOTHING_SWAT; - //êèäàíèå ãðàíàòû + //Throwing Grenades gubAnimSurfaceIndex[ BIGMALE ][ THROW_GRENADE_STANCE ] = BGMSTHRG; gubAnimSurfaceIndex[ BIGMALE ][ LOB_GRENADE_STANCE ] = BGMSLOBG; - //ïåðåêàò + //Rolling gubAnimSurfaceIndex[ BIGMALE ][ ROLL_PRONE_L ] = BGMROLL_PR; gubAnimSurfaceIndex[ BIGMALE ][ ROLL_PRONE_R ] = BGMROLL_PR; @@ -2182,10 +2182,10 @@ void InitAnimationSurfacesPerBodytype( ) gubAnimSurfaceIndex[ STOCKYMALE ][ SWATTING_WK ] = RGMSWKNIFE; gubAnimSurfaceIndex[ STOCKYMALE ][ SWAT_BACKWARDS_WK ] = RGMSWKNIFE; gubAnimSurfaceIndex[ STOCKYMALE ][ SWAT_BACKWARDS_NOTHING ] = RGMNOTHING_SWAT; - //êèäàíèå ãðàíàòû + //Throwing Grenades gubAnimSurfaceIndex[ STOCKYMALE ][ THROW_GRENADE_STANCE ] = RGMSTHRG; gubAnimSurfaceIndex[ STOCKYMALE ][ LOB_GRENADE_STANCE ] = RGMSLOBG; - //ïåðåêàò + //Rolling gubAnimSurfaceIndex[ STOCKYMALE ][ ROLL_PRONE_L ] = RGMROLL_PR; gubAnimSurfaceIndex[ STOCKYMALE ][ ROLL_PRONE_R ] = RGMROLL_PR; @@ -2562,10 +2562,10 @@ void InitAnimationSurfacesPerBodytype( ) gubAnimSurfaceIndex[ REGFEMALE ][ SWAT_BACKWARDS_NOTHING ] = RGFNOTHING_SWAT; gubAnimSurfaceIndex[ REGFEMALE ][ SWATTING_WK ] = RGFSWKNIFE; gubAnimSurfaceIndex[ REGFEMALE ][ SWAT_BACKWARDS_WK ] = RGFSWKNIFE; - //êèäàíèå ãðàíàòû + //Throwing Grenades gubAnimSurfaceIndex[ REGFEMALE ][ THROW_GRENADE_STANCE ] = RGFSTHRG; gubAnimSurfaceIndex[ REGFEMALE ][ LOB_GRENADE_STANCE ] = RGFSLOBG; - //ïåðåêòà + //Rolling gubAnimSurfaceIndex[ REGFEMALE ][ ROLL_PRONE_L ] = RGFROLL_PR; gubAnimSurfaceIndex[ REGFEMALE ][ ROLL_PRONE_R ] = RGFROLL_PR; @@ -3247,8 +3247,7 @@ BOOLEAN LoadAnimationStateInstructions( ) return( FALSE ); } - //ddd ïàòàìóøòà âûëåòàåò ïðè ïðåâûøåíèè 320 çíà÷åíèÿ ;( - //ïðèäåòñÿ ìàñòåðèòü âñïîìîãàòåëüíûé ìàññèâ, ïîòîì êîïèðîâàòü åãî ñîäåðæèìîå â ãóñàíèìèíñò + //ddd Trick for loading more than 320 animation files (?) UINT16 fuckTheBoundz[320][100]; //Read in block @@ -3265,7 +3264,7 @@ BOOLEAN LoadAnimationStateInstructions( ) memcpy(gusAnimInst[i],fuckTheBoundz[i],sizeof(fuckTheBoundz[i])); //gusAnimInst[i][0] = fuckTheBoundz[i][0]; - //! äëÿ ïåðåãîíêè äæà2áèí â ÷èòàåìûé âèä + // For conversion into readable format (?) // char gS[22]; // UINT32 uiNumBytesWritten = 64000; // int ggg[MAX_ANIMATIONS][MAX_FRAMES_PER_ANIM]; @@ -3837,7 +3836,7 @@ UINT16 DetermineSoldierAnimationSurface( SOLDIERTYPE *pSoldier, UINT16 usAnimSta if ( usAltAnimSurface != INVALID_ANIMATION ) { - //ddd{ ñþäà ñòàâèòü àëüòåðíàòèâíóþ àíèìàøêó äëÿ âûñòðåëà èç ïèñòîëåòà + //ddd Put alternative pistol-shot animations here /*if ( usAnimSurface == BGMSTANDAIM2 ) { diff --git a/Tactical/Animation Control.h b/Tactical/Animation Control.h index 38f4129e..8d2d41bb 100644 --- a/Tactical/Animation Control.h +++ b/Tactical/Animation Control.h @@ -7,7 +7,7 @@ // Defines // ####################################################### -#define MAX_ANIMATIONS 350 //!!!!!ddd ïîêà äîáàâèë 30 ëèøíèõ ñëîâòîâ ïîä àíèìàøêè. +#define MAX_ANIMATIONS 350 //!!!!!ddd added 30 new animations #define MAX_FRAMES_PER_ANIM 100 #define MAX_RANDOM_ANIMS_PER_BODYTYPE 8 @@ -548,9 +548,9 @@ enum AnimationStates SWATTING_WK, SWAT_BACKWARDS_WK, SWAT_BACKWARDS_NOTHING, - THROW_GRENADE_STANCE, //ddd äëÿ áàáû àíèìàöèè íåò - èñïîëüçóåì ñòàíäàðòíóþ + THROW_GRENADE_STANCE, //ddd Currently no animation for women. Using the standard. LOB_GRENADE_STANCE, - //ïåðåêàòòû + //Prone rolls ROLL_PRONE_R, ROLL_PRONE_L, diff --git a/Tactical/Animation Data.h b/Tactical/Animation Data.h index 93886033..4bd99e87 100644 --- a/Tactical/Animation Data.h +++ b/Tactical/Animation Data.h @@ -518,15 +518,15 @@ BGMWITHSTONE, BGMSWKNIFE, RGFSWKNIFE, RGMSWKNIFE, -//êèäàíèå ãðàíàòû ñòîÿ ñ âûä. ÷åêè +//Launch grenade (?) BGMSTHRG, RGFSTHRG, RGMSTHRG, -//êèäàíèå ãðàíàòû ñòîÿ ñ âûä. ÷åêè áëèçêîå ðàññò +//Lob grenade BGMSLOBG, RGFSLOBG, RGMSLOBG, -//âíàãëóþ ñòûðèë êîä ó òåðàïåâòà! +//Roll while prone RGMROLL_PR, BGMROLL_PR, RGFROLL_PR, diff --git a/Tactical/Bullets.h b/Tactical/Bullets.h index 4081db2b..689eb99f 100644 --- a/Tactical/Bullets.h +++ b/Tactical/Bullets.h @@ -57,6 +57,7 @@ typedef struct UINT8 ubTilesPerUpdate; UINT16 usClockTicksPerUpdate; SOLDIERTYPE *pFirer; + INT32 sOrigGridNo; // HEADROCK HAM 5.1: Original tile does not necessarily have to be the pFirer's tile INT32 sTargetGridNo; INT16 sHitBy; INT32 iImpact; @@ -73,6 +74,8 @@ typedef struct // the bullet animation functions to create a lightpath for this bullet. This means now all bullets in a tracer // magazine will cause a lightshow (that's the intended result). BOOLEAN fTracer; + // HEADROCK HAM 5: Fragments. They are handled slightly differently by the bullet functions. + BOOLEAN fFragment; } BULLET; extern UINT32 guiNumBullets; @@ -82,6 +85,8 @@ void RemoveBullet( INT32 iBullet ); void StopBullet( INT32 iBullet ); void UpdateBullets( ); BULLET *GetBulletPtr( INT32 iBullet ); +INT32 BulletImpact( SOLDIERTYPE *pFirer, BULLET *pBullet, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iImpact, INT16 sHitBy, UINT8 * pubSpecial ); + void DeleteAllBullets( ); diff --git a/Tactical/Interface Enhanced.cpp b/Tactical/Interface Enhanced.cpp index 3de0b115..566831f5 100644 --- a/Tactical/Interface Enhanced.cpp +++ b/Tactical/Interface Enhanced.cpp @@ -1376,6 +1376,7 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr INT32 threshold; INT32 iRegionsCreated = 0; INT32 iFirstDataRegion = 0; + UINT8 ubRegionOffset = 0; // HEADROCK HAM 5: To make it easier to edit. InitEDBCoords( gpItemDescObject ); @@ -1522,103 +1523,193 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr /////////////////// ACCURACY if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 0 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 0; + } + else + { + ubRegionOffset = 2; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// DAMAGE if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_BLADE|IC_PUNCH|IC_THROWING_KNIFE) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 1 ] ); + ubRegionOffset = 1; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// RANGE if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 2; + } + else + { + ubRegionOffset = 0; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } - + + /////////////////// GUN HANDLING + if ( UsingNewCTHSystem() == TRUE && Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) + { + ubRegionOffset = 3; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); + } + /////////////////// AIMING LEVELS if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 4; + } + else + { + ubRegionOffset = 3; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); + } + + /////////////////// OCTH AIMING BONUS + if ( UsingNewCTHSystem() == false && + (GetFlatAimBonus( gpItemDescObject ) != 0 || Item[gpItemDescObject->usItem].aimbonus != 0) ) + { + ubRegionOffset = 4; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// SCOPE MAGNIFICATION if ( Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); + ubRegionOffset = 5; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); + } + + /////////////////// OCTH MINIMUM RANGE FOR AIMING BONUS + if( UsingNewCTHSystem() == false && + ( Item[gpItemDescObject->usItem].minrangeforaimbonus > 0 || GetMinRangeForAimBonus( gpItemDescObject ) > 0 ) ) + { + ubRegionOffset = 5; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// PROJECTION FACTOR - if ( GetProjectionFactor( gpItemDescObject ) > 1.0f ) + if (UsingNewCTHSystem() == true && + (Item[gpItemDescObject->usItem].projectionfactor > 1.0 || GetProjectionFactor( gpItemDescObject ) > 1.0) ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 5 ] ); + ubRegionOffset = 6; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } + + /////////////////// OCTH TO=HIT BONUS + if (UsingNewCTHSystem() == false && + (Item[gpItemDescObject->usItem].tohitbonus != 0 || GetFlatToHitBonus( gpItemDescObject ) != 0) ) + { + ubRegionOffset = 6; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); + } + + /////////////////// OCTH BEST LASER RANGE + if (UsingNewCTHSystem() == false && + (Item[gpItemDescObject->usItem].bestlaserrange > 0 || GetAverageBestLaserRange( gpItemDescObject ) > 0 ) ) + { + ubRegionOffset = 7; + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); + } + /////////////////// FLASH SUPPRESSION if (IsFlashSuppressorAlt( gpItemDescObject ) == TRUE) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 6 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 7; + } + else + { + ubRegionOffset = 8; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// LOUDNESS if (Weapon[ Item[ gpItemDescObject->usItem ].ubClassIndex ].ubAttackVolume > 0 ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 7 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 8; + } + else + { + ubRegionOffset = 9; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// RELIABILITY { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 8 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 9; + } + else + { + ubRegionOffset = 10; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// REPAIR EASE { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 9 ] ); - } - - /////////////////// MinRangeForAimBonus - { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 10 ] ); - } - - /////////////////// ToHitBonus - { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 11 ] ); + if (UsingNewCTHSystem() == true) + { + ubRegionOffset = 10; + } + else + { + ubRegionOffset = 11; + } + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + ubRegionOffset ] ); } /////////////////// AP TO DRAW if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_PUNCH) && !Item[ gpItemDescObject->usItem].rocketlauncher ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 12 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 13 ] ); } /////////////////// AP TO SINGLE ATTACK { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 13 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 14 ] ); } /////////////////// AP TO BURST if ( Item[gpItemDescObject->usItem].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[gpItemDescObject->usItem].rocketlauncher ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 14 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 15 ] ); } /////////////////// AP TO AUTOFIRE if ( Item[gpItemDescObject->usItem].usItemClass == IC_GUN && !Item[gpItemDescObject->usItem].rocketlauncher ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 15 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 16 ] ); } /////////////////// AP TO RELOAD if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 16 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 17 ] ); } /////////////////// AP TO RELOAD MANUALLY if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher && Weapon[gpItemDescObject->usItem].APsToReloadManually > 0 ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 17 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 18 ] ); } /////////////////// RECOIL X/Y @@ -1626,27 +1717,28 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr { if ( Item[ gpItemDescObject->usItem ].usItemClass == IC_GUN && !Item[ gpItemDescObject->usItem].rocketlauncher && (GetAutofireShotsPerFiveAPs(gpItemDescObject) > 0 || GetShotsPerBurst(gpItemDescObject)> 0 ) ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 18 ] ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 19 ] ); + // HEADROCK HAM 5: One value to rule them all. + // MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 18 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 20 ] ); } } else /////////////////// BIPOD & BURST PENALTY { if( GetBurstPenalty(gpItemDescObject) > 0 ) - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 18 ] ); - if( GetBipodBonus(gpItemDescObject) > 0 ) MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 19 ] ); + if( GetBipodBonus(gpItemDescObject) > 0 ) + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 20 ] ); } /////////////////// BULLETS PER 5 AP if ( Item[ gpItemDescObject->usItem ].usItemClass == IC_GUN && !Item[ gpItemDescObject->usItem].rocketlauncher && GetAutofireShotsPerFiveAPs(gpItemDescObject) > 0 ) { - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 20 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 21 ] ); } /////////////////// AUTOFIRE PENALTY if( UsingNewCTHSystem() == false && GetAutoPenalty(gpItemDescObject) > 0 ) - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 21 ] ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 22 ] ); } } @@ -1899,146 +1991,180 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 1 ] ); } - //////////////////// BLAST RADIUS - if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 - && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 5 - && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 1 ) + // HEADROCK HAM 5 + //////////////////// EXPLODE ON IMPACT + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].fExplodeOnImpact ) { swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 2 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 2 ]); SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); } + //////////////////// BLAST RADIUS + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 + && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 5 + && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 1 ) + { + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 3 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 3 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + } + //////////////////// STUN BLAST RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 1 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 3 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 3 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 4 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 4 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// NOISE BLAST RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 5 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 4 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 4 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 5 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 5 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// TEARGAS START RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 2 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 5 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 5 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 6 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 6 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// MUSTARD GAS START RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 3 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 6 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 6 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 7 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 7 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// LIGHT START RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 4 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 7 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 7 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 8 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 8 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// SMOKE START RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 6 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 8 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 8 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 9 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 9 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// INCENDIARY START RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 8 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 9 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 9 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 2 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 2 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 10 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 10 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); } //////////////////// TEARGAS END RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 2 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 10 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 10 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 11 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 11 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 4 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } //////////////////// MUSTARD GAS END RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 3 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 11 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 11 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 12 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 12 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 4 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } //////////////////// LIGHT END RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 4 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 12 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 12 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 13 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 13 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 4 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } //////////////////// SMOKE END RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 6 ) { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 13 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 13 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 14 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 14 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 4 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } //////////////////// NAPALM END RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 8 ) - { - swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 14 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 14 ]); - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 3 ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 3 ] ); - } - - //////////////////// DURATION - if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 15 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 15 ]); SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 4 ]), pStr ); MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 4 ] ); } - //////////////////// LOUDNESS + //////////////////// DURATION + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 16 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 16 ]); SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 5 ]), pStr ); MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 5 ] ); } - //////////////////// VOLATILITY - if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubVolatility > 0 ) + // HEADROCK HAM 5: Fragmentation + //////////////////// NUMBER OF FRAGMENTS + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) { swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 17 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 17 ]); SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 6 ]), pStr ); MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 6 ] ); } + + //////////////////// FRAGMENT DAMAGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 18 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 18 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 7 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 7 ] ); + } + + //////////////////// FRAGMENT RANGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 19 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 19 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 8 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 8 ] ); + } + + //////////////////// LOUDNESS + { + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 20 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 20 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 9 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 9 ] ); + } + + //////////////////// VOLATILITY + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubVolatility > 0 ) + { + swprintf( pStr, L"%s%s", szUDBGenExplosiveStatsTooltipText[ 21 ], szUDBGenExplosiveStatsExplanationsTooltipText[ 21 ]); + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + 10 ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + 10 ] ); + } } } @@ -2411,6 +2537,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr { swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 1 ], szUDBAdvStatsExplanationsTooltipText[ 1 ]); } + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ] ); } cnt++; } @@ -2823,6 +2951,31 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } } + // HEADROCK HAM 5: Moved here because it makes more sense. + ///////////////////// MAX COUNTER FORCE + if ((CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ) != 0 + || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ) != 0 + || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ) != 0 ) ) + { + if( UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) + { + if (cnt >= sFirstLine && cnt < sLastLine) + { + if (Item[ gpItemDescObject->usItem ].usItemClass & (IC_WEAPON|IC_PUNCH)) + { + swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 44 ], szUDBAdvStatsExplanationsTooltipTextForWeapons[ 44 ]); + } + else + { + swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 44 ], szUDBAdvStatsExplanationsTooltipText[ 44 ]); + } + SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ]), pStr ); + MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ] ); + } + cnt++; + } + } + ///////////////////// MAX COUNTER FORCE MODIFIER if ((GetCounterForceMaxModifier( gpItemDescObject, ANIM_STAND ) != 0 || GetCounterForceMaxModifier( gpItemDescObject, ANIM_CROUCH ) != 0 @@ -2871,6 +3024,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } } + // HEADROCK HAM 5: This is gone now. + /* ///////////////////// COUNTER FORCE FREQUENCY MODIFIER if (GetCounterForceFrequencyModifier( gpItemDescObject, ANIM_STAND ) != 0 || GetCounterForceFrequencyModifier( gpItemDescObject, ANIM_CROUCH ) != 0 @@ -2893,7 +3048,7 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } cnt++; } - } + }*/ ///////////////////// AP MODIFIER if (GetAPBonus( gpItemDescObject ) != 0 ) @@ -3448,30 +3603,8 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr cnt++; } - ///////////////////// MAX COUNTER FORCE - if ((CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ) != 0 - || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ) != 0 - || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ) != 0 ) ) - { - if( UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - if (Item[ gpItemDescObject->usItem ].usItemClass & (IC_WEAPON|IC_PUNCH)) - { - swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 44 ], szUDBAdvStatsExplanationsTooltipTextForWeapons[ 44 ]); - } - else - { - swprintf( pStr, L"%s%s", szUDBAdvStatsTooltipText[ 44 ], szUDBAdvStatsExplanationsTooltipText[ 44 ]); - } - SetRegionFastHelpText( &(gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ]), pStr ); - MSYS_EnableRegion( &gUDBFasthelpRegions[ iFirstDataRegion + (cnt-sFirstLine) ] ); - } - cnt++; - } - } - + // HEADROCK HAM 5: This no longer exists. + /* ///////////////////// COUNTER FORCE FREQUENCY if ((GetCounterForceMaxModifier( gpItemDescObject, ANIM_STAND ) != 0 || GetCounterForceMaxModifier( gpItemDescObject, ANIM_CROUCH ) != 0 @@ -3494,8 +3627,7 @@ void InternalInitEDBTooltipRegion( OBJECTTYPE * gpItemDescObject, UINT32 guiCurr } cnt++; } - } - + }*/ // Flugente FTW 1.1: if ( gGameOptions.fWeaponOverheating ) { @@ -3742,6 +3874,7 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) INT32 cnt; INT16 sOffsetX = 2; INT16 sOffsetY = 1; + UINT8 ubNumLine; if( UsingEDBSystem() == 0 ) return; @@ -3752,137 +3885,232 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) //////////////////// ACCURACY if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 8, gItemDescGenRegions[0][0].sLeft+sOffsetX, gItemDescGenRegions[0][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 0; + } + else + { + ubNumLine = 2; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 8, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// DAMAGE if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_PUNCH|IC_BLADE|IC_THROWING_KNIFE) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 5, gItemDescGenRegions[1][0].sLeft+sOffsetX, gItemDescGenRegions[1][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 1; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 5, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// RANGE if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 4, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 2; + } + else + { + ubNumLine = 0; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 4, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + //////////////////// GUN HANDLING + if ( UsingNewCTHSystem() == TRUE && Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) + { + ubNumLine = 3; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 33, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// ALLOWED AIM LEVELS if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 33, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 4; + } + else + { + ubNumLine = 3; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 32, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + //////////////////// OCTH AIMING BONUS + if ( UsingNewCTHSystem() == false && + (GetFlatAimBonus( gpItemDescObject ) != 0 || Item[gpItemDescObject->usItem].aimbonus != 0) ) + { + ubNumLine = 4; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 15, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// SCOPE MAGNIFICATION - if ( Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) + if ( UsingNewCTHSystem() == true && Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 15, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 5; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 15, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + //////////////////// OCTH MINIMUM RANGE FOR AIMING BONUS + if( UsingNewCTHSystem() == false && + ( Item[gpItemDescObject->usItem].minrangeforaimbonus > 0 || GetMinRangeForAimBonus( gpItemDescObject ) > 0 ) ) + { + ubNumLine = 5; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 27, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// PROJECTION FACTOR - if (GetProjectionFactor( gpItemDescObject ) > 1.0) + if (UsingNewCTHSystem() == true && + (Item[gpItemDescObject->usItem].projectionfactor > 1.0 || GetProjectionFactor( gpItemDescObject ) > 1.0) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 14, gItemDescGenRegions[5][0].sLeft+sOffsetX, gItemDescGenRegions[5][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 6; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 14, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + //////////////////// OCTH TO=HIT BONUS + if (UsingNewCTHSystem() == false && + (Item[gpItemDescObject->usItem].tohitbonus != 0 || GetFlatToHitBonus( gpItemDescObject ) != 0) ) + { + ubNumLine = 6; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 13, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + //////////////////// OCTH BEST LASER RANGE + if (UsingNewCTHSystem() == false && + (Item[gpItemDescObject->usItem].bestlaserrange > 0 || GetAverageBestLaserRange( gpItemDescObject ) > 0 ) ) + { + ubNumLine = 7; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 14, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// FLASH SUPPRESSION if (IsFlashSuppressorAlt( gpItemDescObject )) { + if (UsingNewCTHSystem() == true) + { + ubNumLine = 7; + } + else + { + ubNumLine = 8; + } // HIDE FLASH ICON - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 28, gItemDescGenRegions[6][0].sLeft+sOffsetX, gItemDescGenRegions[6][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 28, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// LOUDNESS if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 17, gItemDescGenRegions[7][0].sLeft+sOffsetX, gItemDescGenRegions[7][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 8; + } + else + { + ubNumLine = 9; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 17, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// RELIABILITY { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 9, gItemDescGenRegions[8][0].sLeft+sOffsetX, gItemDescGenRegions[8][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 9; + } + else + { + ubNumLine = 10; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 9, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// REPAIR EASE { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 10, gItemDescGenRegions[9][0].sLeft+sOffsetX, gItemDescGenRegions[9][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - } - - //////////////////// MinRangeForAimBonus - if( UsingNewCTHSystem() == false ) - { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 27, gItemDescGenRegions[10][0].sLeft+sOffsetX, gItemDescGenRegions[10][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - } - - //////////////////// BestLaserRange - if( UsingNewCTHSystem() == false ) - { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 13, gItemDescGenRegions[11][0].sLeft+sOffsetX, gItemDescGenRegions[11][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + if (UsingNewCTHSystem() == true) + { + ubNumLine = 10; + } + else + { + ubNumLine = 11; + } + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 10, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// DRAW COST if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem].rocketlauncher ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 1, gItemDescGenRegions[13][0].sLeft+sOffsetX, gItemDescGenRegions[13][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 13; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 1, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } //////////////////// SINGLE SHOT COST - GUN if ( Item[gpItemDescObject->usItem].usItemClass == IC_GUN && !Item[gpItemDescObject->usItem].rocketlauncher ) { + ubNumLine = 14; // "NO SINGLE-SHOT" ICON - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 19, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 19, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); if ( !Weapon[gpItemDescObject->usItem].NoSemiAuto ) { // SINGLE SHOT AP ICON overwrites shadow - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[14][0].sLeft+sOffsetX+1, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX+1, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } /////////////////// SINGLE SHOT COST - ROCKET if ( Item[gpItemDescObject->usItem].usItemClass & (IC_GUN|IC_LAUNCHER) && Item[gpItemDescObject->usItem].rocketlauncher ) { + ubNumLine = 14; // SINGLE ROCKET-LAUNCH AP ICON - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 21, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 21, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } /////////////////// SINGLE SHOT COST - GRENADE LAUNCHER if ( Item[gpItemDescObject->usItem].usItemClass == IC_LAUNCHER && !Item[gpItemDescObject->usItem].rocketlauncher && !Weapon[gpItemDescObject->usItem].NoSemiAuto ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 24, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 14; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 24, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } /////////////////// SINGLE SHOT COST - THROWING KNIFE if ( Item[gpItemDescObject->usItem].usItemClass == IC_THROWING_KNIFE ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 22, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 14; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 22, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } /////////////////// SINGLE SHOT COST - STABBING KNIFE if ( Item[gpItemDescObject->usItem].usItemClass == IC_BLADE ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 20, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 14; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 20, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } /////////////////// SINGLE SHOT COST - BLUNT WEAPON if ( Item[gpItemDescObject->usItem].usItemClass == IC_PUNCH ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 26, gItemDescGenRegions[14][0].sLeft+sOffsetX, gItemDescGenRegions[14][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 14; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 26, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } /////////////////// BURST COST - GUN if ( Item[gpItemDescObject->usItem].usItemClass == IC_GUN && !Item[gpItemDescObject->usItem].rocketlauncher ) { + ubNumLine = 15; // "NO BURST" ICON - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 11, gItemDescGenRegions[15][0].sLeft+sOffsetX, gItemDescGenRegions[15][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 11, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); if (GetShotsPerBurst(gpItemDescObject)> 0) { for ( cnt = 0; cnt < GetShotsPerBurst(gpItemDescObject); cnt++ ) { // BURST FIRE ICON overwrites shadow - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[15][0].sLeft+sOffsetX + cnt * (BULLET_WIDTH/2 + 1) +1, gItemDescGenRegions[15][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX + cnt * (BULLET_WIDTH/2 + 1) +1, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } } @@ -3891,21 +4119,23 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) if ( Item[gpItemDescObject->usItem].usItemClass == IC_LAUNCHER && !Item[gpItemDescObject->usItem].rocketlauncher && GetShotsPerBurst(gpItemDescObject)> 0) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 25, gItemDescGenRegions[15][0].sLeft+sOffsetX, gItemDescGenRegions[15][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 15; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 25, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////// AUTOFIRE COST if ( Item[gpItemDescObject->usItem].usItemClass == IC_GUN && !Item[gpItemDescObject->usItem].rocketlauncher ) { + ubNumLine = 16; // "NO-AUTO" ICON - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 12, gItemDescGenRegions[16][0].sLeft+sOffsetX, gItemDescGenRegions[16][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 12, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); if (GetAutofireShotsPerFiveAPs(gpItemDescObject) > 0 ) { for ( cnt = 0; cnt < 10; cnt++ ) { // AUTO FIRE ICON overwrites shadow - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[16][0].sLeft+sOffsetX + cnt * (BULLET_WIDTH/2 + 1) +1, gItemDescGenRegions[16][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 0, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX + cnt * (BULLET_WIDTH/2 + 1) +1, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } } @@ -3913,24 +4143,27 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) ////////////////// RELOAD COST if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 2, gItemDescGenRegions[17][0].sLeft+sOffsetX, gItemDescGenRegions[17][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 17; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 2, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////// MANUAL RELOAD COST if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem ].singleshotrocketlauncher && Weapon[gpItemDescObject->usItem].APsToReloadManually > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 3, gItemDescGenRegions[18][0].sLeft+sOffsetX, gItemDescGenRegions[18][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 18; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 3, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ///////////////// RECOIL X/Y if( UsingNewCTHSystem() == true ) { + ubNumLine = 20; if ( Item[ gpItemDescObject->usItem ].usItemClass == IC_GUN && !Item[ gpItemDescObject->usItem].rocketlauncher && ( GetShotsPerBurst(gpItemDescObject)> 0 || GetAutofireShotsPerFiveAPs(gpItemDescObject) > 0 ) ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 31, gItemDescGenRegions[19][0].sLeft+sOffsetX, gItemDescGenRegions[19][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 32, gItemDescGenRegions[20][0].sLeft+sOffsetX, gItemDescGenRegions[20][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + // HEADROCK HAM 5: One value to rule them all! Line 19 left empty intentionally. + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 31, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } @@ -3938,11 +4171,13 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) { if( GetBurstPenalty(gpItemDescObject) > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 30, gItemDescGenRegions[19][0].sLeft+sOffsetX, gItemDescGenRegions[19][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 19; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 30, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } if( GetBipodBonus(gpItemDescObject) > 0) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 16, gItemDescGenRegions[20][0].sLeft+sOffsetX, gItemDescGenRegions[20][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 20; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 16, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } @@ -3950,13 +4185,15 @@ void DrawWeaponStats( OBJECTTYPE * gpItemDescObject ) if ( Item[ gpItemDescObject->usItem ].usItemClass == IC_GUN && !Item[ gpItemDescObject->usItem].rocketlauncher && GetAutofireShotsPerFiveAPs(gpItemDescObject) > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 7, gItemDescGenRegions[21][0].sLeft+sOffsetX, gItemDescGenRegions[21][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 21; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 7, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ///////////////// AUTOFIRE PENALTY if( UsingNewCTHSystem() == false && GetAutoPenalty(gpItemDescObject) > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 29, gItemDescGenRegions[22][0].sLeft+sOffsetX, gItemDescGenRegions[22][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + ubNumLine = 22; + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoWeaponIcon, 29, gItemDescGenRegions[ubNumLine][0].sLeft+sOffsetX, gItemDescGenRegions[ubNumLine][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } } else if(gubDescBoxPage == 2) @@ -4028,18 +4265,25 @@ void DrawExplosiveStats( OBJECTTYPE * gpItemDescObject ) BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 1, gItemDescGenRegions[1][0].sLeft+sOffsetX, gItemDescGenRegions[1][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } + // HEADROCK HAM 5 + ////////////////////// EXPLODE ON IMPACT + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].fExplodeOnImpact ) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 19, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + ////////////////////// SOUND BLAST if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 5 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 6, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 6, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// STUN BLAST if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 1 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 5, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 5, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// OTHER BLASTS @@ -4047,23 +4291,23 @@ void DrawExplosiveStats( OBJECTTYPE * gpItemDescObject ) && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 1 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType != 5 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 4, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 4, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// START+END RADIUS: TEAR GAS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 2 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 9, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 10, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 9, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 10, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// START+END RADIUS: MUSTARD GAS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 3 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 13, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 14, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 13, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 14, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// START+END RADIUS: LIGHT @@ -4071,41 +4315,60 @@ void DrawExplosiveStats( OBJECTTYPE * gpItemDescObject ) && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 4 ) { // Note light is reversed (large to small) - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 18, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 17, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 18, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 17, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// START+END RADIUS: SMOKE if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 6 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 11, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 12, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 11, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 12, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// START+END RADIUS: NAPALM if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 && Explosive[Item[ gpItemDescObject->usItem].ubClassIndex ].ubType == 8 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 15, gItemDescGenRegions[2][0].sLeft+sOffsetX, gItemDescGenRegions[2][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 16, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 15, gItemDescGenRegions[3][0].sLeft+sOffsetX, gItemDescGenRegions[3][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 16, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// DURATION if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 7, gItemDescGenRegions[4][0].sLeft+sOffsetX, gItemDescGenRegions[4][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 7, gItemDescGenRegions[5][0].sLeft+sOffsetX, gItemDescGenRegions[5][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + // HEADROCK HAM 5: Fragmentation + ////////////////////// NUMBER OF FRAGMENTS + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 20, gItemDescGenRegions[6][0].sLeft+sOffsetX, gItemDescGenRegions[6][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + ////////////////////// FRAGMENT DAMAGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 21, gItemDescGenRegions[7][0].sLeft+sOffsetX, gItemDescGenRegions[7][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + + ////////////////////// FRAGMENT RANGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 22, gItemDescGenRegions[8][0].sLeft+sOffsetX, gItemDescGenRegions[8][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// LOUDNESS { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 2, gItemDescGenRegions[5][0].sLeft+sOffsetX, gItemDescGenRegions[5][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 2, gItemDescGenRegions[9][0].sLeft+sOffsetX, gItemDescGenRegions[9][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } ////////////////////// VOLATILITY if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubVolatility > 0 ) { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 3, gItemDescGenRegions[6][0].sLeft+sOffsetX, gItemDescGenRegions[6][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoExplosiveIcon, 3, gItemDescGenRegions[10][0].sLeft+sOffsetX, gItemDescGenRegions[10][0].sTop+sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); } DrawSecondaryStats( gpItemDescObject ); @@ -4279,7 +4542,7 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) ///////////////////// AIM BONUS MODIFIER if(UsingNewCTHSystem() == false) { - if ( GetAimBonus( gpItemDescObject, 100, 1 ) != 0 ) + if ( GetFlatAimBonus( gpItemDescObject ) != 0 ) { if (cnt >= sFirstLine && cnt < sLastLine) { @@ -4448,6 +4711,22 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) } } + ///////////////////// MAX COUNTER FORCE + // HEADROCK HAM 5: Moved here because it makes more sense. + if (CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ) != 0 + || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ) != 0 + || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ) != 0 ) + { + if( UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) + { + if (cnt >= sFirstLine && cnt < sLastLine) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoAdvancedIcon, 19, gItemDescAdvRegions[cnt-sFirstLine][0].sLeft + sOffsetX, gItemDescAdvRegions[cnt-sFirstLine][0].sTop + sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); + } + cnt++; + } + } + ///////////////////// MAX COUNTER FORCE MODIFIER if (GetCounterForceMaxModifier( gpItemDescObject, ANIM_STAND ) != 0 || GetCounterForceMaxModifier( gpItemDescObject, ANIM_CROUCH ) != 0 @@ -4734,21 +5013,8 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) cnt++; } - ///////////////////// MAX COUNTER FORCE - if (CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ) != 0 - || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ) != 0 - || CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ) != 0 ) - { - if( UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) - { - if (cnt >= sFirstLine && cnt < sLastLine) - { - BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemInfoAdvancedIcon, 17, gItemDescAdvRegions[cnt-sFirstLine][0].sLeft + sOffsetX, gItemDescAdvRegions[cnt-sFirstLine][0].sTop + sOffsetY, VO_BLT_SRCTRANSPARENCY, NULL ); - } - cnt++; - } - } - + // HEADROCK HAM 5: Counter-Force Frequency has been removed from the game in favour of a more realistic system. + /* ///////////////////// COUNTER FORCE FREQUENCY if (CalcCounterForceFrequency( gpItemDescSoldier, gpItemDescObject ) != 0 ) { @@ -4761,7 +5027,7 @@ void DrawAdvancedStats( OBJECTTYPE * gpItemDescObject ) cnt++; } } - + */ // Flugente FTW 1.1 if ( gGameOptions.fWeaponOverheating ) @@ -5171,7 +5437,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { // Set line to draw into - ubNumLine = 0; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 0; + } + else + { + ubNumLine = 2; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5301,7 +5574,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { // Set line to draw into - ubNumLine = 2; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 2; + } + else + { + ubNumLine = 0; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5363,11 +5643,85 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) SetFontForeground( 6 ); } + /////////////// GUN HANDLING + if ( UsingNewCTHSystem() == TRUE && + Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) + { + // Set line to draw into + ubNumLine = 3; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get base Gun Handling value + UINT16 iHandlingValue = Weapon[ gpItemDescObject->usItem ].ubHandling; + + // Get modifier + INT16 iHandlingModifier = (iHandlingValue * GetPercentHandlingModifier( gpItemDescObject , ANIM_STAND )) / 100; + + // Get Final Gun Handling value + UINT16 iFinalHandlingValue = iHandlingValue + iHandlingModifier; + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + swprintf( pStr, L"%d", iHandlingValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print modifier + SetFontForeground( 5 ); + if (iHandlingModifier < 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + } + else if ( iHandlingModifier > 0 ) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + } + // Add positive/negative sign + if ( iHandlingModifier > 0 ) + { + swprintf( pStr, L"+%d", iHandlingModifier ); + } + else if ( iHandlingModifier < 0 ) + { + swprintf( pStr, L"%d", iHandlingModifier ); + } + else + { + swprintf( pStr, L"--" ); + } + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalHandlingValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Reset font color + SetFontForeground( 6 ); + } + /////////////// AIM LEVELS if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER|IC_THROWING_KNIFE) ) { // Set line to draw into - ubNumLine = 3; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 4; + } + else + { + ubNumLine = 3; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5430,8 +5784,9 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) SetFontForeground( 6 ); } - //////////////// SCOPE MAGNIFICATION FACTOR - if ( Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) + //////////////// OCTH AIMING BONUS + if ( UsingNewCTHSystem() == false && + (GetFlatAimBonus( gpItemDescObject ) != 0 || Item[gpItemDescObject->usItem].aimbonus != 0) ) { // Set line to draw into ubNumLine = 4; @@ -5439,19 +5794,74 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; - FLOAT iScopeMagValue; // Get base Magnification value - FLOAT iScopeMagModifier; // Get best Magnification value - FLOAT iFinalScopeMagValue; // Get final Magnification value + // Get base Aim Bonus value + INT16 iAimBonusValue = __max(0, Item[ gpItemDescObject->usItem ].aimbonus); + // Get final Aim Bonus value + INT16 iFinalAimBonusValue = GetFlatAimBonus( gpItemDescObject ); + // Get Aim Bonus modifier + INT16 iAimBonusModifier = iFinalAimBonusValue - iAimBonusValue; - if(UsingNewCTHSystem()==true){ - iScopeMagValue = __max(1.0f, Item[ gpItemDescObject->usItem ].scopemagfactor); - iScopeMagModifier = GetHighestScopeMagnificationFactor( gpItemDescObject ); - iFinalScopeMagValue = __max( iScopeMagValue, iScopeMagModifier ); - } else { - iScopeMagValue = __max(0.0f, Item[ gpItemDescObject->usItem ].aimbonus); - iScopeMagModifier = (FLOAT)GetFlatAimBonus( gpItemDescObject ); - iFinalScopeMagValue = __max( iScopeMagValue, iScopeMagModifier ); + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + if (iAimBonusValue != 0) + { + swprintf( pStr, L"%d", iAimBonusValue ); } + else + { + swprintf( pStr, L"--" ); + } + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Modifier + SetFontForeground( 5 ); + if (iAimBonusModifier > 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + swprintf( pStr, L"+%d", iAimBonusModifier ); + } + else if (iAimBonusValue < 0) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + swprintf( pStr, L"%d", iAimBonusModifier ); + } + else + { + swprintf( pStr, L"--" ); + } + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Final Value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalAimBonusValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + + //////////////// SCOPE MAGNIFICATION FACTOR + if ( UsingNewCTHSystem() == true && Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) + { + // Set line to draw into + ubNumLine = 5; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get base Magnification value + FLOAT iScopeMagValue = __max(1.0f, Item[ gpItemDescObject->usItem ].scopemagfactor); + // Get best Magnification value + FLOAT iScopeMagModifier = GetHighestScopeMagnificationFactor( gpItemDescObject ); + // Get final Magnification value + FLOAT iFinalScopeMagValue = __max( iScopeMagValue, iScopeMagModifier ); // Print base value SetFontForeground( 5 ); @@ -5477,7 +5887,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) } else { - swprintf( pStr, L"--", iScopeMagModifier ); + swprintf( pStr, L"--" ); } sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; @@ -5493,8 +5903,9 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) mprintf( usX, usY, pStr ); } - ///////////////// (LASER) PROJECTION FACTOR - if (GetProjectionFactor( gpItemDescObject ) > 1.0 ) + /////////////////// OCTH MINIMUM RANGE FOR AIMING BONUS + if( UsingNewCTHSystem() == false && + ( Item[gpItemDescObject->usItem].minrangeforaimbonus > 0 || GetMinRangeForAimBonus( gpItemDescObject ) > 0 ) ) { // Set line to draw into ubNumLine = 5; @@ -5502,19 +5913,83 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; - FLOAT iProjectionValue; // Get base Projection value - FLOAT iProjectionModifier; // Get best Projection value - FLOAT iFinalProjectionValue; // Get final Projection value + // Get base Minimum Range For Aim Bonus value + INT16 iMinRangeForAimBonusValue = Item[gpItemDescObject->usItem].minrangeforaimbonus / 10; - if(UsingNewCTHSystem() == true){ - iProjectionValue = __max(1.0f, Item[ gpItemDescObject->usItem ].projectionfactor); - iProjectionModifier = GetProjectionFactor( gpItemDescObject ); - iFinalProjectionValue = __max( iProjectionValue, iProjectionModifier ); - } else { - iProjectionValue = __max(0.0f, Item[ gpItemDescObject->usItem ].bestlaserrange / 10); - iProjectionModifier = (FLOAT)GetAverageBestLaserRange( gpItemDescObject ) / 10; - iFinalProjectionValue = __max( iProjectionValue, iProjectionModifier ); + // Get final Minimum Range For Aim Bonus value + INT16 iFinalMinRangeForAimBonusValue = GetMinRangeForAimBonus(gpItemDescObject) / 10; + + // Get Minimum Range For Aim Bonus modifier + INT16 iMinRangeForAimBonusModifier = iFinalMinRangeForAimBonusValue - iMinRangeForAimBonusValue; + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + if (iMinRangeForAimBonusValue > 0) + { + swprintf( pStr, L"%d", iMinRangeForAimBonusValue ); } + else + { + swprintf( pStr, L"--" ); + } + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print modifier + SetFontForeground( 5 ); + if (iMinRangeForAimBonusModifier < 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + } + else if ( iMinRangeForAimBonusModifier > 0 ) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + } + // Add positive/negative sign + if ( iMinRangeForAimBonusModifier > 0 ) + { + swprintf( pStr, L"+%d", iMinRangeForAimBonusModifier ); + } + else if ( iMinRangeForAimBonusModifier < 0 ) + { + swprintf( pStr, L"%d", iMinRangeForAimBonusModifier ); + } + else + { + swprintf( pStr, L"--" ); + } + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalMinRangeForAimBonusValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + + ///////////////// (LASER) PROJECTION FACTOR + if (UsingNewCTHSystem() == true && + (Item[gpItemDescObject->usItem].projectionfactor > 1.0 || GetProjectionFactor( gpItemDescObject ) > 1.0) ) + { + // Set line to draw into + ubNumLine = 6; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get base Projection value + FLOAT iProjectionValue = __max(1.0f, Item[ gpItemDescObject->usItem ].projectionfactor); + // Get best Projection value + FLOAT iProjectionModifier = GetProjectionFactor( gpItemDescObject ); + // Get final Projection value + FLOAT iFinalProjectionValue = __max( iProjectionValue, iProjectionModifier ); // Print base value SetFontForeground( 5 ); @@ -5540,7 +6015,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) } else { - swprintf( pStr, L"--", iProjectionModifier ); + swprintf( pStr, L"--" ); } sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; @@ -5556,11 +6031,143 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) mprintf( usX, usY, pStr ); } + ///////////////// OCTH TO-HIT BONUS + if (UsingNewCTHSystem() == false && + (Item[gpItemDescObject->usItem].tohitbonus != 0 || GetFlatToHitBonus( gpItemDescObject ) != 0) ) + { + // Set line to draw into + ubNumLine = 6; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get base To Hit value + INT16 iToHitValue = Item[ gpItemDescObject->usItem ].tohitbonus; + // Get final Projection value + INT16 iFinalToHitValue = GetFlatToHitBonus( gpItemDescObject ); + // Get To Hit Modifier value + INT16 iToHitModifier = iFinalToHitValue - iToHitValue; + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + if (iToHitValue != 0) + { + swprintf( pStr, L"%d", iToHitValue ); + } + else + { + swprintf( pStr, L"--", iToHitValue ); + } + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Modifier + SetFontForeground( 5 ); + if (iToHitModifier > 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + swprintf( pStr, L"+%d", iToHitModifier ); + } + else if (iToHitModifier < 0) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + swprintf( pStr, L"%d", iToHitModifier ); + } + else + { + swprintf( pStr, L"--" ); + } + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Final Value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalToHitValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + } + + ///////////////// OCTH BEST LASER RANGE + if (UsingNewCTHSystem() == false && + ( Item[gpItemDescObject->usItem].bestlaserrange > 0 || GetAverageBestLaserRange( gpItemDescObject ) > 0 ) ) + { + // Set line to draw into + ubNumLine = 7; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get base Best Laser Range value + INT16 iBestLaserRangeValue = Item[ gpItemDescObject->usItem ].bestlaserrange / 10; + // Get final Best Laser Range value + INT16 iFinalBestLaserRangeValue = GetAverageBestLaserRange( gpItemDescObject ) / 10; + // Get Best Laser Range Modifier value + INT16 iBestLaserRangeModifier = iFinalBestLaserRangeValue - iBestLaserRangeValue; + + // Print base value + SetFontForeground( 5 ); + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + if (iBestLaserRangeValue > 0) + { + swprintf( pStr, L"%d", iBestLaserRangeValue ); + } + else + { + swprintf( pStr, L"--", iBestLaserRangeValue ); + } + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Modifier + SetFontForeground( 5 ); + if (iBestLaserRangeModifier > 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + swprintf( pStr, L"+%d", iBestLaserRangeModifier ); + } + else if (iBestLaserRangeModifier < 0) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + swprintf( pStr, L"%d", iBestLaserRangeModifier ); + } + else + { + swprintf( pStr, L"--" ); + } + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print Final Value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalBestLaserRangeValue ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + ///////////////// FLASH SUPPRESSION if (IsFlashSuppressorAlt( gpItemDescObject ) == TRUE) { // Set line to draw into - ubNumLine = 6; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 7; + } + else + { + ubNumLine = 8; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5611,7 +6218,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) ) { // Set line to draw into - ubNumLine = 7; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 8; + } + else + { + ubNumLine = 9; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5676,7 +6290,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) /////////////////// RELIABILITY { // Set line to draw into - ubNumLine = 8; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 9; + } + else + { + ubNumLine = 10; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5752,7 +6373,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) /////////////////// REPAIR EASE { // Set line to draw into - ubNumLine = 9; + if (UsingNewCTHSystem() == true) + { + ubNumLine = 10; + } + else + { + ubNumLine = 11; + } // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -5801,160 +6429,6 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) mprintf( usX, usY, pStr ); } - /////////////////// MinRangeForAimBonus - if( UsingNewCTHSystem() == false ) - { - // Set line to draw into - ubNumLine = 10; - // Set Y coordinates - sTop = gItemDescGenRegions[ubNumLine][1].sTop; - sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; - - // Get base Reliability value - INT16 iMinRangeForAimBonusValue = Item[gpItemDescObject->usItem].minrangeforaimbonus / 10; - - // Get final Reliability value - INT16 iFinalMinRangeForAimBonusValue = GetMinRangeForAimBonus(gpItemDescObject) / 10; - - // Get Reliability modifier - INT16 iMinRangeForAimBonusModifier = iFinalMinRangeForAimBonusValue - iMinRangeForAimBonusValue; - - // Print base value - SetFontForeground( 5 ); - sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; - if (iMinRangeForAimBonusValue < 0) - { - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - swprintf( pStr, L"%d", iMinRangeForAimBonusValue ); - } - else if ( iMinRangeForAimBonusValue > 0 ) - { - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - swprintf( pStr, L"+%d", iMinRangeForAimBonusValue ); - } - else - { - swprintf( pStr, L"--" ); - } - - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - - // Print modifier - SetFontForeground( 5 ); - if (iMinRangeForAimBonusModifier < 0) - { - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - } - else if ( iMinRangeForAimBonusModifier > 0 ) - { - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - } - // Add positive/negative sign - if ( iMinRangeForAimBonusModifier > 0 ) - { - swprintf( pStr, L"+%d", iMinRangeForAimBonusModifier ); - } - else if ( iMinRangeForAimBonusModifier < 0 ) - { - swprintf( pStr, L"%d", iMinRangeForAimBonusModifier ); - } - else - { - swprintf( pStr, L"--" ); - } - sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - - // Print final value - SetFontForeground( FONT_MCOLOR_WHITE ); - sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; - swprintf( pStr, L"%d", iFinalMinRangeForAimBonusValue ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - } - - /////////////////// ToHitBonus - if( UsingNewCTHSystem() == false ) - { - // Set line to draw into - ubNumLine = 11; - // Set Y coordinates - sTop = gItemDescGenRegions[ubNumLine][1].sTop; - sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; - - // Get base Reliability value - INT16 iToHitBonusValue = Item[gpItemDescObject->usItem].tohitbonus; - - // Get final Reliability value - INT16 iFinalToHitBonusValue = GetFlatToHitBonus(gpItemDescObject); - - // Get Reliability modifier - INT16 iToHitBonusModifier = iFinalToHitBonusValue - iToHitBonusValue; - - // Print base value - SetFontForeground( 5 ); - sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; - if (iToHitBonusValue < 0) - { - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - swprintf( pStr, L"%d", iToHitBonusValue ); - } - else if ( iToHitBonusValue > 0 ) - { - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - swprintf( pStr, L"+%d", iToHitBonusValue ); - } - else - { - swprintf( pStr, L"--" ); - } - - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - - // Print modifier - SetFontForeground( 5 ); - if (iToHitBonusModifier < 0) - { - SetFontForeground( ITEMDESC_FONTNEGATIVE ); - } - else if ( iToHitBonusModifier > 0 ) - { - SetFontForeground( ITEMDESC_FONTPOSITIVE ); - } - // Add positive/negative sign - if ( iToHitBonusModifier > 0 ) - { - swprintf( pStr, L"+%d", iToHitBonusModifier ); - } - else if ( iToHitBonusModifier < 0 ) - { - swprintf( pStr, L"%d", iToHitBonusModifier ); - } - else - { - swprintf( pStr, L"--" ); - } - sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - - // Print final value - SetFontForeground( FONT_MCOLOR_WHITE ); - sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; - sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; - swprintf( pStr, L"%d", iFinalToHitBonusValue ); - FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); - mprintf( usX, usY, pStr ); - } - ///////////////////// DRAW AP if ( Item[ gpItemDescObject->usItem ].usItemClass & (IC_GUN|IC_LAUNCHER) && !Item[ gpItemDescObject->usItem].rocketlauncher ) { @@ -6334,6 +6808,9 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) if ( Item[ gpItemDescObject->usItem ].usItemClass == IC_GUN && !Item[ gpItemDescObject->usItem].rocketlauncher && GetShotsPerBurst(gpItemDescObject)> 0 || GetAutofireShotsPerFiveAPs(gpItemDescObject)) { + // HEADROCK HAM 5: One value to rule them all. + // Set line to draw into + ubNumLine = 20; INT8 iFinalRecoilX = 0; INT8 iFinalRecoilY = 0; @@ -6348,14 +6825,10 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) if (iRecoilX == -127) { iRecoilX = 0; } // -127 means "invalid". These guns don't actually have any recoil parameters. if (iRecoilY == -127) { iRecoilY = 0; } - // Get Recoil Modifiers - INT16 iRecoilXModifier = iFinalRecoilX - iRecoilX; - INT16 iRecoilYModifier = iFinalRecoilY - iRecoilY; + FLOAT dBaseRecoil = sqrt((FLOAT)((iRecoilX * iRecoilX)+(iRecoilY * iRecoilY))); + FLOAT dFinalRecoil = sqrt((FLOAT)((iFinalRecoilX * iFinalRecoilX) + (iFinalRecoilY * iFinalRecoilY))); + FLOAT dRecoilModifier = dFinalRecoil - dBaseRecoil; - // RECOIL X - - // Set line to draw into - ubNumLine = 19; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -6364,28 +6837,28 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) SetFontForeground( 5 ); sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; - swprintf( pStr, L"%d", iRecoilX ); + swprintf( pStr, L"%3.1f", dBaseRecoil ); FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); mprintf( usX, usY, pStr ); // Print modifier SetFontForeground( 5 ); - if ( (iRecoilXModifier < 0 && iRecoilX > 0) || (iRecoilXModifier > 0 && iRecoilX < 0) ) + if ( (dRecoilModifier < 0 && dBaseRecoil > 0) || (dRecoilModifier > 0 && dBaseRecoil < 0) ) { SetFontForeground( ITEMDESC_FONTPOSITIVE ); } - else if ( (iRecoilXModifier > 0 && iRecoilX > 0) || (iRecoilXModifier < 0 && iRecoilX < 0) ) + else if ( (dRecoilModifier > 0 && dBaseRecoil > 0) || (dRecoilModifier < 0 && dBaseRecoil < 0) ) { SetFontForeground( ITEMDESC_FONTNEGATIVE ); } // Add positive/negative sign - if ( iRecoilXModifier > 0 ) + if ( dRecoilModifier > 0 ) { - swprintf( pStr, L"+%d", iRecoilXModifier ); + swprintf( pStr, L"+%3.1f", dRecoilModifier ); } - else if ( iRecoilXModifier < 0 ) + else if ( dRecoilModifier < 0 ) { - swprintf( pStr, L"%d", iRecoilXModifier ); + swprintf( pStr, L"%3.1f", dRecoilModifier ); } else { @@ -6400,13 +6873,14 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) SetFontForeground( FONT_MCOLOR_WHITE ); sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; - swprintf( pStr, L"%d", iFinalRecoilX ); + swprintf( pStr, L"%3.1f", dFinalRecoil ); FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); mprintf( usX, usY, pStr ); // Reset font color SetFontForeground( 6 ); - + + /* // RECOIL Y // Set line to draw into @@ -6461,6 +6935,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) // Reset font color SetFontForeground( 6 ); + */ } } else ///////////////// BIPOD & BURST PENALTY @@ -6634,6 +7109,7 @@ void DrawWeaponValues( OBJECTTYPE * gpItemDescObject ) { // Set line to draw into ubNumLine = 21; + // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7157,11 +7633,13 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) mprintf( usX, usY, pStr ); } + // HEADROCK HAM 5: Pushed everyone one line down to make room for Contact Explosives. + ////////////////////// BLAST RADIUS if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration == 0 ) { // Set line to draw into - ubNumLine = 2; + ubNumLine = 3; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7202,7 +7680,7 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { // Set line to draw into - ubNumLine = 2; + ubNumLine = 3; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7251,7 +7729,7 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { // Set line to draw into - ubNumLine = 3; + ubNumLine = 4; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7300,7 +7778,7 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubDuration > 0 ) { // Set line to draw into - ubNumLine = 4; + ubNumLine = 5; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7337,10 +7815,134 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) mprintf( usX, usY, pStr ); } + // HEADROCK HAM 5: FRAGMENTATIONS + //////////////////// NUMBER OF FRAGMENTS + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + // Set line to draw into + ubNumLine = 6; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get final Num Fragments + INT16 iFinalNumFragments = Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments; + + // Get base Num Fragments + INT16 iNumFragments = iFinalNumFragments; + + // Print base value + SetFontForeground( 5 ); + + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + swprintf( pStr, L"%d", iNumFragments ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // no modifier + SetFontForeground( 5 ); + swprintf( pStr, L"--" ); + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalNumFragments ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + + //////////////////// FRAGMENT DAMAGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + // Set line to draw into + ubNumLine = 7; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get final Num Fragments + INT16 iFinalFragDamage = Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubFragDamage; + + // Get base Num Fragments + INT16 iFragDamage = iFinalFragDamage; + + // Print base value + SetFontForeground( 5 ); + + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + swprintf( pStr, L"%d", iFragDamage ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // no modifier + SetFontForeground( 5 ); + swprintf( pStr, L"--" ); + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalFragDamage ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + + //////////////////// FRAG RANGE + if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].usNumFragments > 0 ) + { + // Set line to draw into + ubNumLine = 8; + // Set Y coordinates + sTop = gItemDescGenRegions[ubNumLine][1].sTop; + sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; + + // Get final Num Fragments + INT16 iFinalFragRange = Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubFragRange / CELL_X_SIZE; + + // Get base Num Fragments + INT16 iFragRange = iFinalFragRange; + + // Print base value + SetFontForeground( 5 ); + + sLeft = gItemDescGenRegions[ubNumLine][1].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][1].sRight - sLeft; + swprintf( pStr, L"%d", iFragRange ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // no modifier + SetFontForeground( 5 ); + swprintf( pStr, L"--" ); + sLeft = gItemDescGenRegions[ubNumLine][2].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][2].sRight - sLeft; + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + + // Print final value + SetFontForeground( FONT_MCOLOR_WHITE ); + sLeft = gItemDescGenRegions[ubNumLine][3].sLeft; + sWidth = gItemDescGenRegions[ubNumLine][3].sRight - sLeft; + swprintf( pStr, L"%d", iFinalFragRange ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + mprintf( usX, usY, pStr ); + } + //////////////////// LOUDNESS { // Set line to draw into - ubNumLine = 5; + ubNumLine = 9; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -7381,7 +7983,7 @@ void DrawExplosiveValues( OBJECTTYPE * gpItemDescObject ) if ( Explosive[Item[ gpItemDescObject->usItem ].ubClassIndex ].ubVolatility > 0 ) { // Set line to draw into - ubNumLine = 6; + ubNumLine = 10; // Set Y coordinates sTop = gItemDescGenRegions[ubNumLine][1].sTop; sHeight = gItemDescGenRegions[ubNumLine][1].sBottom - sTop; @@ -8604,6 +9206,48 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) cnt++; } + ///////////////////// MAX COUNTER FORCE + // HEADROCK HAM 5: Moved here because it makes more sense. + iFloatModifier[0] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_STAND ); + iFloatModifier[1] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_CROUCH ); + iFloatModifier[2] = CalcCounterForceMax( gpItemDescSoldier, gpItemDescObject, ANIM_PRONE ); + if ((iFloatModifier[0] != 0 || iFloatModifier[1] != 0 || iFloatModifier[2] != 0) && UsingNewCTHSystem() == true && Item[gpItemDescObject->usItem].usItemClass == IC_GUN ) + { + if (cnt >= sFirstLine && cnt < sLastLine) + { + // Set Y coordinates + sTop = gItemDescAdvRegions[cnt-sFirstLine][1].sTop; + sHeight = gItemDescAdvRegions[cnt-sFirstLine][1].sBottom - sTop; + + // Print Values + for (UINT8 cnt2 = 0; cnt2 < 3; cnt2++) + { + SetFontForeground( 5 ); + sLeft = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sLeft; + sWidth = gItemDescAdvRegions[cnt-sFirstLine][cnt2+1].sRight - sLeft; + if (iFloatModifier[cnt2] > 0) + { + SetFontForeground( ITEMDESC_FONTPOSITIVE ); + swprintf( pStr, L"%3.1f", iFloatModifier[cnt2] ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + } + else if (iFloatModifier[cnt2] < 0) + { + SetFontForeground( ITEMDESC_FONTNEGATIVE ); + swprintf( pStr, L"%3.1f", iFloatModifier[cnt2] ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + } + else + { + swprintf( pStr, L"--" ); + FindFontCenterCoordinates( sLeft, sTop, sWidth, sHeight, pStr, BLOCKFONT2, &usX, &usY); + } + mprintf( usX, usY, pStr ); + } + } + cnt++; + } + ///////////////////// MAX COUNTER FORCE MODIFIER iModifier[0] = GetCounterForceMaxModifier( gpItemDescObject, ANIM_STAND ); iModifier[1] = GetCounterForceMaxModifier( gpItemDescObject, ANIM_CROUCH ); @@ -10046,7 +10690,9 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } - + + // HEADROCK HAM 5: Counter-Force Frequency has been removed from the game in favour of a more realistic system. + /* ///////////////////// COUNTER FORCE FREQUENCY iModifier[0] = CalcCounterForceFrequency( gpItemDescSoldier, gpItemDescObject ); iModifier[1] = iModifier[0]; @@ -10087,7 +10733,8 @@ void DrawAdvancedValues( OBJECTTYPE *gpItemDescObject ) } cnt++; } - + */ + // Flugente FTW 1.1 if ( gGameOptions.fWeaponOverheating ) { diff --git a/Tactical/Interface Items.cpp b/Tactical/Interface Items.cpp index 12c49a35..32697554 100644 --- a/Tactical/Interface Items.cpp +++ b/Tactical/Interface Items.cpp @@ -265,12 +265,22 @@ UINT32 guiAttachmentSlot; UINT32 guiItemGraphic; UINT32 guiMoneyGraphicsForDescBox; UINT32 guiBullet; +// HEADROCK HAM 5: Icon indicating a transformation is available. +UINT32 guiTransformIconGraphic; BOOLEAN gfInItemDescBox = FALSE; UINT32 guiCurrentItemDescriptionScreen=0; OBJECTTYPE *gpItemDescObject = NULL; // HEADROCK HAM 4: Remembers the object that was open before an attachment desc is opened on top of it. OBJECTTYPE *gpItemDescPrevObject = NULL; +// HEADROCK HAM 5: This is a clone, used to contain an item's details when the item has been destroyed while the box is open. +OBJECTTYPE gCloneItemDescObject; BOOLEAN gfItemDescObjectIsAttachment = FALSE; +OBJECTTYPE *gpItemDescOrigAttachmentObject = NULL; + +// HEADROCK HAM 5: Used by the callback confirming whether we want to transform. +TransformInfoStruct * gTransformInProgress; +UINT32 guiTransformInProgressPrevScreen; + CHAR16 gzItemName[ SIZE_ITEM_NAME ]; CHAR16 gzItemDesc[ SIZE_ITEM_INFO ]; CHAR16 gzItemPros[ SIZE_ITEM_PROS ]; @@ -300,6 +310,25 @@ INT32 giActiveAttachmentPopup = -1; POPUP* gEquipPopups[NUM_INV_SLOTS]; INT32 giActiveEquipPopup = -1; +// HEADROCK HAM 5: Item Transformation Popups +BOOLEAN gfItemDescTransformPopupInitialized = FALSE; +POPUP* gItemDescTransformPopup; +BOOLEAN gfItemDescTransformPopupVisible = FALSE; +BOOLEAN gfSkipDestroyTransformPopup = FALSE; // This makes sure the +// And forward declarations... +void TransformationMenuPopup_Hide(void); +void TransformationMenuPopup_Transform(TransformInfoStruct * Transform); +BOOLEAN TransformationMenuPopup_TestValid(TransformInfoStruct * Transform); +void ItemDescTransformRegionCallback( MOUSE_REGION *pRegion, INT32 reason ); +void TransformationMenuPopup_Unjam(); +void TransformationMenuPopup_SplitCrate( UINT16 usMagazineItem ); +void TransformationMenuPopup_SplitCrateInInventory( ); +void TransformFromItemDescBox( TransformInfoStruct * Transform); +void ConfirmTransformationMessageBoxCallBack( UINT8 bExitValue ); + +// HEADROCK HAM 5: The maximum number of attachment asterisks shown for an item. +UINT32 guiAttachmentAsterisks; +#define MAX_NUM_ASTERISKS 10 void BtnMoneyButtonCallback(GUI_BUTTON *btn,INT32 reason); UINT32 guiMoneyButtonBtn[OLD_MAX_ATTACHMENTS_101]; @@ -385,10 +414,14 @@ void ItemDescTabButtonOff( UINT8 ubItemDescTabButtonIndex ); INT32 giInvDescAdvButtonUpImage; INT32 giInvDescAdvButtonDownImage; INT32 giInvDescAdvButton[2] = {-1, -1}; +// HEADROCK HAM 5: Item Transformation click region. +MOUSE_REGION gInvDescTransformRegion; void ItemDescAdvButtonCallback( GUI_BUTTON *btn, INT32 reason ); void ItemDescAdvButtonOn( UINT8 ubItemDescAdvButtonIndex ); void ItemDescAdvButtonOff( UINT8 ubItemDescAdvButtonIndex ); void ItemDescAdvButtonCheck( void ); +// HEADROCK HAM 5: Item Adjustment Button(s) +void ItemDescTransformRegionCallback( MOUSE_REGION *pRegion, INT32 reason ); // the done descrition button callback void ItemDescDoneButtonCallback( GUI_BUTTON *btn, INT32 reason ); @@ -1544,6 +1577,786 @@ BOOLEAN CheckActivationStatus(SOLDIERTYPE *pSoldier, INT16 cSlot, INT16 bSlot, I return(FALSE); } +// THE_BOB: functions for mag-making gizmo (TODO: clean up this code) +std::vector * getSoldierGuns( SOLDIERTYPE *pTeamSoldier ) +{ + UINT32 bLoop; + std::vector * guns = new std::vector; + + // Search for gun in soldier inventory + for (bLoop = 0; bLoop < pTeamSoldier->inv.size(); bLoop++) + { + if ( (Item[pTeamSoldier->inv[bLoop].usItem].usItemClass & IC_GUN) + || (Item[pTeamSoldier->inv[bLoop].usItem].usItemClass == IC_LAUNCHER) ) + { + guns->push_back( &(pTeamSoldier->inv[bLoop]) ); + } + } + + // no guns found, get rid of the vector + if( guns->size() < 1 ) { + delete guns; + guns = NULL; + } + + return guns; +} + +INT16 pocketTypeInSlot(SOLDIERTYPE *pSoldier, INT16 sPocket){ + + INT16 lbePocket = ITEM_NOT_FOUND; + + if((UsingNewInventorySystem() == false) && !oldInv[sPocket]) + return lbePocket; + if((pSoldier->flags.uiStatusFlags & SOLDIER_VEHICLE) && UsingNewInventorySystem() == true && !vehicleInv[sPocket]) + return lbePocket; + + switch (icClass[sPocket]) + { + case THIGH_PACK: + case VEST_PACK: + case COMBAT_PACK: + case BACKPACK: + lbePocket = + (pSoldier->inv[icLBE[sPocket]].exists() == false) + ? LoadBearingEquipment[Item[icDefault[sPocket]].ubClassIndex].lbePocketIndex[icPocket[sPocket]] + : LoadBearingEquipment[Item[pSoldier->inv[icLBE[sPocket]].usItem].ubClassIndex].lbePocketIndex[icPocket[sPocket]]; + + break; + case LBE_POCKET: + if ( sPocket == VESTPOCKPOS ) + lbePocket = 0; + else if ( sPocket == LTHIGHPOCKPOS ) + lbePocket = 1; + else if ( sPocket == RTHIGHPOCKPOS ) + lbePocket = 2; + else if ( sPocket == CPACKPOCKPOS ) + lbePocket = 3; + else if ( sPocket == BPACKPOCKPOS ) + lbePocket = 4; + + case OTHER_POCKET: + + if ( sPocket == GUNSLINGPOCKPOS ) // Gun Sling + lbePocket = 1; + else + lbePocket = 2; + break; + + default: + // ? + break; + } + + return lbePocket; +} + +//THE_BOB: mag-making popups +static POPUP * sPocketPopup = NULL; +static BOOL sPocketPopupInitialized = FALSE; + +void popupCallbackAmmo(UINT16 item, UINT16 pocket, SOLDIERTYPE* pSoldier ){ + + if(!(gTacticalStatus.uiFlags & INCOMBAT)) + { + UINT16 magSize, ubShotsLeft; + OBJECTTYPE *pObj = NULL; + OBJECTTYPE tempClip; + OBJECTTYPE tempStack; + bool clipCreated; + UINT32 newItem = 0; + INT16 pocketType = pocketTypeInSlot(pSoldier, pocket); + UINT8 capacity = 0; + + if ( pocketType != -1 ){ + capacity = LBEPocketType[pocketTypeInSlot(pSoldier, pocket)].ItemCapacityPerSize[ Item[item].ItemSize ]; + } else { + capacity = 1; + } + + UINT8 bLoop; + + + // find an ammo crate that can be used to make requested mag + for(UINT16 i = 0; i < pInventoryPoolList.size(); i++) + { + + if( Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubMagType >= AMMO_BOX // item is ammo box/crate + && Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubAmmoType // same ammo type + == Magazine[ Item[item].ubClassIndex ].ubAmmoType // as the mag we found? + && Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubCalibre // same calibre + == Magazine[ Item[item].ubClassIndex ].ubCalibre // as the mag we found? + ) + { + pObj = &pInventoryPoolList[i].object; // found ammo crate + break; + } + } + + if(!pObj) { + sPocketPopup->hide(); + return; + } + + //find the ammo item we want to try and create + newItem = item; + + //Create a stack of up to 5 "newItem" clips + tempStack.initialize(); + clipCreated = false; + ubShotsLeft = (*pObj)[0]->data.ubShotsLeft; + for(UINT8 clip = 0; clip < capacity; clip++) + { + magSize = Magazine[ Item[item].ubClassIndex ].ubMagSize; + if(ubShotsLeft < magSize) + magSize = ubShotsLeft; + + if(CreateAmmo(newItem, &tempClip, magSize)) + { + tempStack.AddObjectsToStack(tempClip, -1, pSoldier, NUM_INV_SLOTS, MAX_OBJECTS_PER_SLOT); + ubShotsLeft -= magSize; + clipCreated = true; + if(ubShotsLeft < 1) + break; + } + + } + //Try to place the stack somewhere on the active merc + if(clipCreated == true) + { + clipCreated = false; + bLoop = tempStack.ubNumberOfObjects; + while(tempStack.ubNumberOfObjects > 0) + { + if(pocket != -1) + { + pSoldier->inv[pocket].AddObjectsToStack(tempStack, bLoop, pSoldier, pocket); + } + else + { + bLoop--; + } + if(bLoop < 1) + break; + } + if(tempStack.ubNumberOfObjects < 1) + clipCreated = true; + else + { + //Try to place stack on ground + if( AutoPlaceObjectToWorld(pSoldier, &tempStack) ) + { + clipCreated = true; + if(guiCurrentScreen == GAME_SCREEN) + NotifySoldiersToLookforItems( ); + } + } + } + if(clipCreated == true) + { + (*pObj)[0]->data.ubShotsLeft = ubShotsLeft; + } + if((*pObj)[0]->data.ubShotsLeft < 1) + pObj->RemoveObjectsFromStack(1); + + sPocketPopup->hide(); + return; + } + else + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pMapInventoryErrorString[6] ); + + sPocketPopup->hide(); + return; + } + +} + +void popupCallbackPlaceLeastDamagedFromStack(OBJECTTYPE * pObj, UINT16 pocket, SOLDIERTYPE* pSoldier ){ + + // can't be sure enough that it will fit + if ( CanItemFitInPosition(pSoldier, pObj, pocket, false) && !pSoldier->inv[pocket].exists() ) { + + // if this is a stack, try to find the least damaged object + + if( pObj->ubNumberOfObjects > 1 ){ + + UINT8 numObjectsToPlace; // try to fit as many objects as possible + + { + INT16 pocketType = pocketTypeInSlot( pSoldier, pocket ); + + if( pocketType != -1 ){ + numObjectsToPlace = min( pObj->objectStack.size(), LBEPocketType[ pocketType ].ItemCapacityPerSize[ Item[pObj->usItem].ItemSize ] ); + } else { + numObjectsToPlace = 1; + } + } + + for (UINT8 j = 1; j <= numObjectsToPlace; j++){ + + UINT16 i = 0, leastDamagedIndex = 0; + INT16 leastDamagedStatus = 0; + + StackedObjects::iterator p = pObj->objectStack.begin(); + while(p != pObj->objectStack.end()) { + + if( p->data.objectStatus > leastDamagedStatus ){ + leastDamagedIndex = i; + leastDamagedStatus = p->data.objectStatus; + } + i++;p++; + } + + OBJECTTYPE pObjTmp; + pObjTmp.initialize(); + + if( pObj->RemoveObjectAtIndex(leastDamagedIndex, &pObjTmp) ) + PlaceObject( pSoldier, pocket, &pObjTmp ); + } + + } else { + PlaceObject( pSoldier, pocket, pObj ); + } + + } + + sPocketPopup->hide(); +} + +// THE_BOB: quick equip popups +extern BOOLEAN CanPlayerUseSectorInventory( SOLDIERTYPE *pSelectedSoldier ); + +extern void RenderTeamRegionBackground(); +void createMagPopupAfter(SOLDIERTYPE *pSoldier){ // after showing the menu, this callback marks the interface as dirty and redraws it + + fTeamPanelDirty = TRUE; + fMapPanelDirty = TRUE; + RenderTeamRegionBackground(); + +} + +INT16 getStatusOfLeastDamagedItemInStack( OBJECTTYPE * stack ){ + + INT16 leastDamagedStatus = 0; + + // find the status of the least damaged item on the stack + if( stack->ubNumberOfObjects > 1 ){ // (unless there's only one item on the stack) + + StackedObjects::iterator p = stack->objectStack.begin(); + while(p != stack->objectStack.end()) { + + if( p->data.objectStatus > leastDamagedStatus ){ + leastDamagedStatus = p->data.objectStatus; + } + + p++; + } + + } else { + leastDamagedStatus = stack->objectStack.begin()->data.objectStatus; + } + + return leastDamagedStatus; +} + +// ugly hack to pretend that weapon class/type defines are bitfields +static const int pow2[] = {1,2,4,8,16,32,64,128,256}; // I'm a funny man. + +std::map findLeastDamagedStackForPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, int itemClass = -1, int weaponClass = -1, int weaponType = -1, int attachType = -1 ){ + + std::map bestItems; + std::map bestItemsStatus; + + for(UINT16 i = 0; i < pInventoryPoolList.size(); i++) + { + + OBJECTTYPE * currentStack = &pInventoryPoolList[i].object; + UINT16 currentItem = currentStack->usItem; + + if( ( ( itemClass == -1 ) || Item[currentItem].usItemClass & itemClass ) + && ( ( attachType == -1 ) || Item[currentItem].nasAttachmentClass == attachType ) + // weapon checks + && ( ( ( weaponClass == -1 ) || ( weaponClass == 0 && Weapon[currentItem].ubWeaponClass == weaponClass ) || pow2[Weapon[currentItem].ubWeaponClass] & weaponClass ) ) + && ( ( ( weaponType == -1 ) || ( weaponType == 0 && Weapon[currentItem].ubWeaponType == weaponType ) || pow2[Weapon[currentItem].ubWeaponType] & weaponType ) ) + // if type/class conditions are met, do more expensive check on whether it will fit + && CanItemFitInPosition(pSoldier, currentStack, sPocket, FALSE) + ) + { + + INT16 leastDamagedStatus = getStatusOfLeastDamagedItemInStack( currentStack ); + + if( bestItemsStatus[ currentItem ] < leastDamagedStatus ){ // either not indexed yet or worse then current + + bestItemsStatus[ currentItem ] = leastDamagedStatus; + bestItems[ currentItem ] = currentStack; + + } + + } // found item + + } // inv loop + + return bestItems; + +} + +// add generic items of some class to the popup, picking the best stacks in sector + +void addItemsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup, int itemClass = -1, int weaponClass = -1, int weaponType = -1, int attachType = -1 ){ + + std::map bestItems = findLeastDamagedStackForPopup( pSoldier, sPocket, itemClass, weaponClass, weaponType, attachType ); + + UINT8 optsTotal = 0, numObjectsToPlace = 1; + INT16 pocketType = pocketTypeInSlot( pSoldier, sPocket ); + POPUP * currPopup = popup; + + for(std::map::iterator itr = bestItems.begin(); itr != bestItems.end(); ++itr){ + + if( optsTotal > 0 && optsTotal%15 == 0 ){ // divide to subBoxes every 10 items + + POPUP * currPopupTmp = currPopup->addSubMenuOption( new std::wstring(L"more...") ); // the new popup + POPUP_SUB_POPUP_OPTION * currSubPopupTmp = currPopup->getSubPopupOption( currPopup->subPopupOptionCount-1 ); // the sub-popup option in prev popup + + + // where to put the new box? + if( currPopup->Position.iX + 200 > SCREEN_WIDTH){ + + // near screen edge, put it below the original box + currSubPopupTmp->setPopupPosition( popup->Position.iX, + popup->Position.iY + popup->getCurrentHeight(), + POPUP_POSITION_TOP_LEFT); + + } else { + // we still got room, position the next box to the right of the previous one + currSubPopupTmp->setPopupPosition( 10, + 0, + POPUP_POSITION_RELATIVE); + + } + + + currPopup = currPopupTmp; // swap popups, now adding options to the new sub-popup + } + + if( pocketType != -1 ){ + numObjectsToPlace = min( itr->second->objectStack.size(), LBEPocketType[ pocketType ].ItemCapacityPerSize[ Item[itr->second->usItem].ItemSize ] ); + } else { + numObjectsToPlace = 1; + } + + if( numObjectsToPlace > 1 ){ // add number of items that will be placed + + static CHAR16 pStr[ 100 ]; + swprintf( pStr, L"%s (%d)", Item[ itr->first ].szItemName, numObjectsToPlace ); + + currPopup->addOption( + &std::wstring( pStr ), + new popupCallbackFunction3(&popupCallbackPlaceLeastDamagedFromStack,itr->second,sPocket,pSoldier) + ); + + } else { + currPopup->addOption( + &std::wstring( Item[ itr->first ].szItemName ), + new popupCallbackFunction3(&popupCallbackPlaceLeastDamagedFromStack,itr->second,sPocket,pSoldier) + ); + } + + optsTotal++; + + } + +} + +void addArmorToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_ARMOUR ); + +} + +void addLBEToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_LBEGEAR ); + +} + +void addWeaponsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_WEAPON ); + +} + +void addWeaponGroupsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + POPUP * subPopup = NULL; + + subPopup = popup->addSubMenuOption( new std::wstring(L"Guns") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10, + 10, + POPUP_POSITION_RELATIVE ); + + + std::map bestItems = findLeastDamagedStackForPopup( pSoldier, sPocket, IC_GUN, + pow2[HANDGUNCLASS] + pow2[SMGCLASS] + pow2[RIFLECLASS] + pow2[MGCLASS] + pow2[SHOTGUNCLASS], + pow2[GUN_PISTOL] + pow2[GUN_M_PISTOL] + pow2[GUN_SMG] + pow2[GUN_RIFLE] + pow2[GUN_SN_RIFLE] + pow2[GUN_AS_RIFLE] + pow2[GUN_LMG] + pow2[GUN_SHOTGUN], + 0); + + POPUP* weaponTypePopup; + UINT8 weaponTypeCtr; + for( weaponTypeCtr = 1; weaponTypeCtr <= 8; weaponTypeCtr++ ){ + + weaponTypePopup = subPopup->addSubMenuOption( new std::wstring( WeaponType[weaponTypeCtr] ) ); + + for(std::map::iterator itr = bestItems.begin(); itr != bestItems.end(); ++itr){ + + if ( Weapon[ itr->first ].ubWeaponType == weaponTypeCtr ) + weaponTypePopup->addOption( + &std::wstring( Item[ itr->first ].szItemName ), + new popupCallbackFunction3(&popupCallbackPlaceLeastDamagedFromStack,itr->second,sPocket,pSoldier) + ); + } + + } + + + subPopup = popup->addSubMenuOption( new std::wstring(L"Grenade launchers") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10, + 10, + POPUP_POSITION_RELATIVE ); + addItemsToPocketPopup( pSoldier, sPocket, subPopup, IC_LAUNCHER, -1, -1, 0 ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Rocket launchers") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10, + 10, + POPUP_POSITION_RELATIVE ); + addItemsToPocketPopup( pSoldier, sPocket, subPopup, IC_GUN, -1, 0, 0); + + + subPopup = popup->addSubMenuOption( new std::wstring(L"Melee & thrown weapons") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10, + 10, + POPUP_POSITION_RELATIVE ); + addItemsToPocketPopup( pSoldier, sPocket, subPopup, IC_BLADE+IC_THROWING_KNIFE, pow2[KNIFECLASS] ); + +} + +void addGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_GRENADE, -1, -1, 0 ); + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_GRENADE, -1, -1, AC_DEFAULT1 ); + +} + +void addRifleGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_GRENADE, -1, -1, AC_GRENADE ); + +} + +void addRocketAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_GRENADE, -1, -1, AC_ROCKET ); + +} + +void addKitsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_MEDKIT + IC_KIT ); + +} + +void addBombsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_BOMB ); + +} + +void addMiscToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_MISC, -1, -1, 1 ); + +} + +void addFaceGearToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + addItemsToPocketPopup( pSoldier, sPocket, popup, IC_FACE ); + +} + + +void addAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ){ + + // get the guns on current soldier + std::vector * guns = getSoldierGuns(pSoldier); + + if( guns != NULL && guns->size() > 0) + { + INT16 lbePocket = pocketTypeInSlot(pSoldier, sPocket); + + for(std::vector::iterator gun=guns->begin(); gun != guns->end(); ++gun) + { + UINT8 ammoFound = 0; + + POPUP_OPTION * o = popup->addOption( &std::wstring( Item[ (*gun)->usItem ].szItemName ), NULL ); + o->color_shade = COLOR_LTGREY; + //o->color_background = COLOR_LTGREY; + + //find the ammo item we want to try and create + for(UINT32 loop = 0; loop < MAXITEMS; loop++) + { + if(Item[loop].usItemClass == IC_AMMO) + { + if( Magazine[Item[loop].ubClassIndex].ubCalibre == Weapon[ (*gun)->usItem ].ubCalibre + && Magazine[Item[loop].ubClassIndex].ubMagSize == GetMagSize((*gun)) + && ( lbePocket == -1 || LBEPocketType[lbePocket].ItemCapacityPerSize[ Item[loop].ItemSize ] > 0 ) ) + { // found ammo for gun, look for its ammo crate in sector. + + for(UINT16 i = 0; i < pInventoryPoolList.size(); i++) + { // TODO: index ammo crates in sector, don't loop over entire inventory for each mag/gun + + if( Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubMagType >= AMMO_BOX // item is ammo box/crate + && Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubAmmoType // same ammo type + == Magazine[ Item[loop].ubClassIndex ].ubAmmoType // as the mag we found? + && Magazine[ Item[pInventoryPoolList[i].object.usItem].ubClassIndex ].ubCalibre // same calibre + == Magazine[ Item[loop].ubClassIndex ].ubCalibre // as the mag we found? + ) + { + ammoFound++; + UINT8 capacity = 0; + + if( lbePocket != -1 ){ + UINT16 ammoLeft = pInventoryPoolList[i].object.objectStack.begin()->data.ubShotsLeft; + UINT16 magSize = Magazine[ Item[loop].ubClassIndex ].ubMagSize; + + UINT8 maxPerPocket = LBEPocketType[pocketTypeInSlot(pSoldier, sPocket)].ItemCapacityPerSize[ Item[loop].ItemSize ]; + + capacity = min( maxPerPocket, UINT8(ammoLeft/magSize) ); + } + else if( CanItemFitInPosition(pSoldier, &pInventoryPoolList[i].object, sPocket, FALSE) ){ + capacity = 1; + } else { + continue; + } + + static CHAR16 pStr[ 100 ]; + swprintf( pStr, L"%s (%d)", Item[loop].szItemName,capacity ); + + popup->addOption( &std::wstring( pStr ), new popupCallbackFunction3(&popupCallbackAmmo,loop,sPocket,pSoldier) ); + + } // found ammo crate, crate matches mag + } // inv loop + } // mag matches + } // mag found + } // mag loop + + if (!ammoFound){ + POPUP_OPTION * o = popup->addOption( &std::wstring( L"- no matching ammo -" ), NULL ); + o->color_shade = COLOR_RED; + } + + }// gun loop + + delete guns; + } // found guns + else + { + POPUP_OPTION * o = popup->addOption( &std::wstring( L"- no guns in inventory -" ), NULL ); + o->color_shade = COLOR_RED; + } + +} + +POPUP * createPopupForPocket( SOLDIERTYPE *pSoldier, INT16 sPocket ){ + + if( !( + guiCurrentItemDescriptionScreen == MAP_SCREEN + && fShowMapInventoryPool + && ( ( Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ].sSectorX == sSelMapX ) + && ( Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ].sSectorY == sSelMapY ) + && ( Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ].bSectorZ == iCurrentMapSectorZ ) + ) + && CanPlayerUseSectorInventory( &Menptr[ gCharactersList[ bSelectedInfoChar ].usSolID ] ) + ) ) + { + return NULL; + } + + INT16 sX, sY; + // get pocket type under cursor + INT16 lbePocket = pocketTypeInSlot(pSoldier, sPocket); + /* + if ( lbePocket != -1 ) { + */ + if (!sPocketPopupInitialized) { + sPocketPopup = new POPUP("Pocket popup"); + sPocketPopup->setCallback(POPUP_CALLBACK_HIDE, new popupCallbackFunction( createMagPopupAfter,pSoldier ) ); + sPocketPopupInitialized = true; + } else { + sPocketPopup->~POPUP(); + sPocketPopup = new POPUP("Pocket popup"); + sPocketPopup->setCallback(POPUP_CALLBACK_HIDE, new popupCallbackFunction( createMagPopupAfter,pSoldier ) ); + } + + sX = gSMInvData[ sPocket ].sX; + sY = gSMInvData[ sPocket ].sY; + + UINT8 thisPopupsPositionType; + if ( sX < 170 && sY < 180 ){ + thisPopupsPositionType = POPUP_POSITION_TOP_LEFT; + } else if ( sX > 170 && sY < 180 ){ + thisPopupsPositionType = POPUP_POSITION_TOP_RIGHT; + } else if ( sX > 170 && sY > 180 ){ + thisPopupsPositionType = POPUP_POSITION_BOTTOM_RIGHT; + } else if ( sX < 170 && sY > 180 ){ + thisPopupsPositionType = POPUP_POSITION_BOTTOM_LEFT; + } else { + thisPopupsPositionType = POPUP_POSITION_TOP_LEFT; + } + + sPocketPopup->setPosition( sX + 12, + /* Put it near the current slot */ sY + 32, + thisPopupsPositionType); + return sPocketPopup; +/* + } // identified pocket + else { + return NULL; + } +*/ +} + +void PocketPopupFull( SOLDIERTYPE *pSoldier, INT16 sPocket ){ + + POPUP * popup = createPopupForPocket( pSoldier, sPocket ); + + if( popup != NULL ){ + + POPUP * subPopup = NULL; + + subPopup = popup->addSubMenuOption( new std::wstring(L"Weapons") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addWeaponGroupsToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Ammo") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addAmmoToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Armor") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addArmorToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"LBE") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addLBEToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Grenades") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addGrenadesToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Bombs") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addBombsToPocketPopup( pSoldier, sPocket, subPopup ); + + subPopup = popup->addSubMenuOption( new std::wstring(L"Face Gear") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addFaceGearToPocketPopup( pSoldier, sPocket, subPopup ); + + popup->show(); + } + +} + +/* + typedef enum eLBE_CLASS // Designation of lbeClass +{ + THIGH_PACK=1, + VEST_PACK, + COMBAT_PACK, + BACKPACK, + LBE_POCKET, + OTHER_POCKET +}; + +typedef enum ePOCKET_TYPE +{ + NO_POCKET_TYPE = 0, + GUNSLING_POCKET_TYPE = 1, + KNIFE_POCKET_TYPE = 2, + VEHICLE_POCKET_TYPE = 3, +}; +*/ + +UINT16 gsPocketUnderCursor; + +void PocketPopupDefault( SOLDIERTYPE *pSoldier, INT16 sPocket ){ + + POPUP * popup = createPopupForPocket( pSoldier, sPocket ); + + if( popup ){ + + switch (sPocket){ + case HELMETPOS: + case VESTPOS: + case LEGPOS: + addArmorToPocketPopup( pSoldier, sPocket, popup ); + break; + + case HEAD1POS: + case HEAD2POS: + addFaceGearToPocketPopup( pSoldier, sPocket, popup ); + break; + + case HANDPOS: + addWeaponGroupsToPocketPopup( pSoldier, sPocket, popup ); + break; + case SECONDHANDPOS: + addWeaponGroupsToPocketPopup( pSoldier, sPocket, popup ); + break; + + case VESTPOCKPOS: + case BPACKPOCKPOS: + case CPACKPOCKPOS: + case LTHIGHPOCKPOS: + case RTHIGHPOCKPOS: + addLBEToPocketPopup( pSoldier, sPocket, popup ); + break; + + case GUNSLINGPOCKPOS: + addWeaponsToPocketPopup( pSoldier, sPocket, popup ); + break; + + case KNIFEPOCKPOS: + addWeaponsToPocketPopup( pSoldier, sPocket, popup ); + break; + + default: + UINT8 pocketType = pocketTypeInSlot(pSoldier,sPocket); + + if( LBEPocketPopup.find(pocketType) == LBEPocketPopup.end() ){ + // default for LBE slots - grenades + ammo for merc's guns + addAmmoToPocketPopup( pSoldier, sPocket, popup ); + + POPUP * subPopup = popup->addSubMenuOption( new std::wstring(L"Grenades") ); + popup->getSubPopupOption( popup->subPopupOptionCount-1 )->setPopupPosition( 10,10,POPUP_POSITION_RELATIVE ); + addGrenadesToPocketPopup( pSoldier, sPocket, subPopup ); + } else { + // popup definition for this slot exists - apply it to the popup + gsPocketUnderCursor = sPocket; + LBEPocketPopup[pocketType].applyToBox( popup ); + } + + + } + + popup->show(); + + } + +} + +// THE_BOB: end of inventory popups + + void INVRenderINVPanelItem( SOLDIERTYPE *pSoldier, INT16 sPocket, UINT8 fDirtyLevel ) { // CHRISL: Only run if we're looking at a legitimate pocket @@ -1580,7 +2393,10 @@ void INVRenderINVPanelItem( SOLDIERTYPE *pSoldier, INT16 sPocket, UINT8 fDirtyLe case VEST_PACK: case COMBAT_PACK: case BACKPACK: - lbePocket = (pSoldier->inv[icLBE[sPocket]].exists() == false) ? LoadBearingEquipment[Item[icDefault[sPocket]].ubClassIndex].lbePocketIndex[icPocket[sPocket]] : LoadBearingEquipment[Item[pSoldier->inv[icLBE[sPocket]].usItem].ubClassIndex].lbePocketIndex[icPocket[sPocket]]; + lbePocket = + (pSoldier->inv[icLBE[sPocket]].exists() == false) + ? LoadBearingEquipment[Item[icDefault[sPocket]].ubClassIndex].lbePocketIndex[icPocket[sPocket]] + : LoadBearingEquipment[Item[pSoldier->inv[icLBE[sPocket]].usItem].ubClassIndex].lbePocketIndex[icPocket[sPocket]]; iClass = Item[pSoldier->inv[sPocket].usItem].usItemClass; if(icLBE[sPocket] == BPACKPOCKPOS && !(pSoldier->flags.ZipperFlag) && (gTacticalStatus.uiFlags & INCOMBAT)) lbePocket = 0; @@ -3090,6 +3906,463 @@ void INVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObjec } } +// HEADROCK HAM 5.1: Render item in a BigItem sector inventory slot. +// This function works largely like the one above it, with several exceptions. For one, the BigItemPic is used, +// which allows us to add lots of data. Since this is only used in the sector inventory, we can forgo things +// like dirtylevels and just draw everything here. +void MAPINVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObject, UINT32 uiItemGraphicNum, INT16 sX, INT16 sY, INT16 sWidth, INT16 sHeight, BOOLEAN fOutline, INT16 sOutlineColor ) +{ + UINT16 uiStringLength; + INVTYPE *pItem; + ETRLEObject *pTrav; + UINT32 usHeight, usWidth; + INT16 sCenX, sCenY, sNewY, sNewX; + HVOBJECT hVObject; + BOOLEAN fLineSplit = FALSE; + INT16 sFontX2 = 0, sFontY2 = 0; + INT16 sFontX = 0, sFontY = 0; + + static CHAR16 pStr[ 100 ], pStr2[ 100 ]; + + if ( pObject->exists() == false ) + { + return; + } + + pItem = &Item[ pObject->usItem ]; + + // Get the video object for this BigItem image. We get this fed from the previous function (inventory render) + // which already selected the proper image. + GetVideoObject( &hVObject, uiItemGraphicNum ); + + // Check height and width... + pTrav = &(hVObject->pETRLEObject[ 0 ] ); + usHeight = (UINT32)pTrav->usHeight; + usWidth = (UINT32)pTrav->usWidth; + + // Center in the slot. Dimensions are also supplied to us by the calling function. + sCenX = sX + (INT16)( abs( sWidth - (double)usWidth ) / 2 ) - pTrav->sOffsetX; + sCenY = sY + (INT16)( abs( sHeight - (double)usHeight ) / 2 ) - pTrav->sOffsetY; + + // If option, draw item shadow. + if(gGameSettings.fOptions[ TOPTION_SHOW_ITEM_SHADOW ]) + { + BltVideoObjectOutlineShadow( guiSAVEBUFFER, hVObject, 0, sCenX - 2, sCenY + 2 ); + } + // Draw the item on-screen. + BltVideoObject( uiBuffer , hVObject, 0, sCenX, sCenY , VO_BLT_SRCTRANSPARENCY,NULL ); + + // OUTLINE + if (fOutline) + { + DrawItemOutlineZoomedInventory( sX, sY, sX+sWidth, sY+sHeight, sOutlineColor, uiBuffer ); + } + + ////////////////////////////////// + // Data display + // + // Given the larger size of the slots used in zoom view, it is possible to add significant amounts of information. + // This function draws more info than a small-item render. Note the changes below. + + SetFontBackground( FONT_MCOLOR_BLACK ); + + ///////////////// # OF ITEMS /////////////// + if ( pObject->ubNumberOfObjects > 1 ) + { + // Set font properties. + SetFont( FONT14ARIAL ); + SetFontForeground( FONT_GRAY4 ); + + // Get number of objects in this slot + swprintf( pStr, L"x%d", pObject->ubNumberOfObjects ); + + // Get length of string + uiStringLength=StringPixLength(pStr, ITEM_FONT ); + // We draw close to the bottom-left corner of the slot. + // Locate starting X. + sNewX = (sX + sWidth - uiStringLength) - 10; + // Locate starting Y + sNewY = (sY + sHeight) - 13; + + // Restore background + if ( uiBuffer == guiSAVEBUFFER ) + { + RestoreExternBackgroundRect( sNewX, sNewY, sWidth, sHeight ); + } + + // Print + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + } + + //////////////////// GUN DATA ////////////////// + if ( pItem->usItemClass == IC_GUN && !Item[pObject->usItem].rocketlauncher ) + { + //////////////// AMMO REMAINING + + // Set font properties + SetFont( LARGEFONT1 ); + // Get color from ammo details + UINT8 ubAmmoColor = AmmoTypes[(*pObject)[0]->data.gun.ubGunAmmoType].fontColour; + SetFontForeground ( ubAmmoColor ); + + sNewX = sX + 5; + sNewY = ((sY + sHeight) - GetFontHeight( LARGEFONT1 )) - 2; + + // HEADROCK HAM 3.4: Get estimate of bullets left. + if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) + { + // Soldier doesn't know. + swprintf( pStr, L"%s", "??" ); + } + else + { + swprintf( pStr, L"%d", (*pObject)[0]->data.gun.ubGunShotsLeft ); + } + + // Restore background + if ( uiBuffer == guiSAVEBUFFER ) + { + RestoreExternBackgroundRect( sNewX, sNewY, 20, 15 ); + } + + // Print + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + + // Record how long this text was in pixels. + UINT16 usAmmoWidth = StringPixLength( pStr, LARGEFONT1 ); + + // Set font properties + SetFontForeground( FONT_GRAY4 ); + swprintf( pStr, L"/" ); + sNewX += usAmmoWidth; + mprintf( sNewX, sNewY, pStr ); + usAmmoWidth = StringPixLength( pStr, LARGEFONT1 ); + + //////////////// AMMO MAX CAPACITY + + // Set font properties + SetFontForeground( FONT_GRAY4 ); + SetFont( TINYFONT1 ); + + // Find difference in width and height. + INT16 sFontHeightDifference = 1; + //INT16 sFontHeightDifference = GetFontHeight( LARGEFONT1 ) - GetFontHeight( FONT14ARIAL ); + sNewX += usAmmoWidth; + sNewY += sFontHeightDifference; + + // Print total magazine size + swprintf( pStr, L"%d", GetMagSize(pObject) ); + + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + + ///////////////// Display 'JAMMED' if we are jammed + if ( (*pObject)[0]->data.gun.bGunAmmoStatus < 0 ) + { + SetFont( FONT12ARIAL ); + SetFontBackground( FONT_MCOLOR_BLACK ); + SetFontForeground( FONT_MCOLOR_RED ); + + swprintf( pStr, TacticalStr[ JAMMED_ITEM_STR ] ); + + VarFindFontCenterCoordinates( sX, sY, sWidth, sHeight , FONT12ARIAL, &sNewX, &sNewY, pStr ); + + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + } + + // Reset font color + SetFontForeground( FONT_MCOLOR_DKGRAY ); + } + + //////////////////// AMMO DATA ////////////////// + if ( pItem->usItemClass & IC_AMMO ) + { + //////////////// AMMO REMAINING + + // Set font properties + SetFont( LARGEFONT1 ); + // Get color from ammo details + UINT8 ubAmmoColor = AmmoTypes[Magazine[Item[ pObject->usItem ].ubClassIndex].ubAmmoType].fontColour; + SetFontForeground ( ubAmmoColor ); + + sNewX = sX + 5; + sNewY = ((sY + sHeight) - GetFontHeight( LARGEFONT1 )) - 2; + + // HEADROCK HAM 3.4: Get estimate of bullets left. + if ( (gTacticalStatus.uiFlags & TURNBASED) && (gTacticalStatus.uiFlags & INCOMBAT) ) + { + // Soldier doesn't know. + swprintf( pStr, L"%s", "??" ); + } + else + { + swprintf( pStr, L"%d", (*pObject)[0]->data.ubShotsLeft ); + } + + // Restore background + if ( uiBuffer == guiSAVEBUFFER ) + { + RestoreExternBackgroundRect( sNewX, sNewY, 20, 15 ); + } + + // Print + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + + // Record how long this text was in pixels. + UINT16 usAmmoWidth = StringPixLength( pStr, LARGEFONT1 ); + + // Set font properties + SetFontForeground( FONT_GRAY4 ); + swprintf( pStr, L"/" ); + sNewX += usAmmoWidth; + mprintf( sNewX, sNewY, pStr ); + usAmmoWidth = StringPixLength( pStr, LARGEFONT1 ); + + //////////////// AMMO MAX CAPACITY + + // Set font properties + SetFontForeground( FONT_GRAY4 ); + SetFont( TINYFONT1 ); + + // Find difference in width and height. + INT16 sFontHeightDifference = 1; + //INT16 sFontHeightDifference = GetFontHeight( LARGEFONT1 ) - GetFontHeight( FONT14ARIAL ); + sNewX += usAmmoWidth; + sNewY += sFontHeightDifference; + + // Print total magazine size + swprintf( pStr, L"%d", Magazine[Item[ pObject->usItem ].ubClassIndex].ubMagSize ); + + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + + // Reset font color + SetFontForeground( FONT_MCOLOR_DKGRAY ); + } + + ////////// LBE ///////////// + if((UsingNewInventorySystem() == true) && pObject->HasAnyActiveLBEs(pSoldier, 0) ) + { + INT16 sOffsetX = 0; + INT16 sOffsetY = 0; + + LBENODE* pLBE = NULL; + UINT16 usCountLBEItems = 0; + + pLBE = pObject->GetLBEPointer(0); + UINT32 lClass = pLBE->lbeClass; + + std::vector pocketKey; + + switch (lClass) + { + case THIGH_PACK: + GetLBESlots(LTHIGHPOCKPOS, pocketKey); + break; + case VEST_PACK: + GetLBESlots(VESTPOCKPOS, pocketKey); + break; + case COMBAT_PACK: + GetLBESlots(CPACKPOCKPOS, pocketKey); + break; + case BACKPACK: + GetLBESlots(BPACKPOCKPOS, pocketKey); + break; + } + + for(unsigned int cnt=0; cntinv[cnt].exists() == true) + usCountLBEItems++; + } + + GetVideoObject( &hVObject, guiAttachmentAsterisks ); + + // Draw large yellow asterisk. + pTrav = &(hVObject->pETRLEObject[ 3 ] ); + sOffsetX += (UINT32)pTrav->usWidth; + + sNewX = ((sX + sWidth) - 6) - sOffsetX; + sNewY = sY + 4; + BltVideoObject( uiBuffer , hVObject, 3, sNewX, sNewY , VO_BLT_SRCTRANSPARENCY,NULL ); + + usCountLBEItems--; + for (INT16 x = 0; x < usCountLBEItems; x++) + { + // Draw small blue asterisk + pTrav = &(hVObject->pETRLEObject[ 4 ] ); + sOffsetX += ((x+1) % 2) * (UINT32)pTrav->usWidth; + sOffsetY = (x % 2) * ((UINT32)pTrav->usHeight + 1); + + sNewX = ((sX + sWidth) - 6) - sOffsetX; + sNewY = sY + 4 + sOffsetY; + BltVideoObject( uiBuffer , hVObject, 4, sNewX, sNewY , VO_BLT_SRCTRANSPARENCY,NULL ); + } + } + ////////// ATTACHMENTS ///////////// + // Attachments are displayed as a series of asterisks rather than the original one asterisk. + // There's the option here to use differently colored (or shaped) asterisks by editing the STI. + else if ( ItemHasAttachments( pObject, pSoldier, 0 ) && !(Item[pObject->usItem].usItemClass & IC_AMMO) ) + { + // This offset is used to draw the asterisks one after the other. + INT16 sOffsetX = 0; + INT16 sOffsetY = 0; + + // Record the number of asterisks we'll need from each type. + INT8 iCurAsterisk = 0; + UINT16 uiNumAttachments = 0; + UINT16 uiNumAttachmentsGeneral = 0; + UINT16 uiNumAttachmentsGL = 0; + UINT16 uiNumAttachmentsOptical = 0; + UINT16 uiNumAttachmentsRecoil = 0; + + // Iterate through the attachments. + if (pObject->exists() == true) + { + for (attachmentList::iterator iter = (*pObject)[0]->attachments.begin(); iter != (*pObject)[0]->attachments.end(); ++iter) + { + if (iter->exists() == false) + { + continue; + } + + iCurAsterisk = ATTACHMENT_GENERAL; + if (Item[iter->usItem].grenadelauncher ) + { + //iCurAsterisk = ATTACHMENT_GL; + uiNumAttachmentsGL++; + } + else if (Item[iter->usItem].visionrangebonus > 0 || Item[iter->usItem].dayvisionrangebonus > 0 || + Item[iter->usItem].nightvisionrangebonus> 0 || Item[iter->usItem].brightlightvisionrangebonus > 0 || + Item[iter->usItem].cavevisionrangebonus > 0 ) + { + iCurAsterisk = ATTACHMENT_OPTICAL; + } + else + { + if (UsingNewCTHSystem() == true) + { + if (Item[iter->usItem].scopemagfactor > 1.0f || Item[iter->usItem].projectionfactor > 1.0f ) + + { + iCurAsterisk = ATTACHMENT_OPTICAL; + } + else + { + for (INT8 x = 0; x < 3; x++) + { + if (Item[iter->usItem].counterforceaccuracymodifier[x] > 0 || + Item[iter->usItem].maxcounterforcemodifier[x] > 0 || + Item[iter->usItem].PercentRecoilModifier < 0 || + Item[iter->usItem].RecoilModifierX < 0 || + Item[iter->usItem].RecoilModifierY < 0 ) + { + iCurAsterisk = ATTACHMENT_RECOILREDUCTION; + } + } + } + } + else + { + if ( (Item[iter->usItem].minrangeforaimbonus > 0 && Item[iter->usItem].aimbonus > 0 ) || + (Item[iter->usItem].bestlaserrange > 0 && Item[iter->usItem].tohitbonus > 0 ) ) + { + iCurAsterisk = ATTACHMENT_OPTICAL; + } + else if ( Item[iter->usItem].autofiretohitbonus > 0 || Item[iter->usItem].bursttohitbonus > 0 || + Item[iter->usItem].bipod > 0 ) + { + iCurAsterisk = ATTACHMENT_RECOILREDUCTION; + } + } + } + uiNumAttachments++; + } + + uiNumAttachmentsGeneral = uiNumAttachments - uiNumAttachmentsGL; + + if (uiNumAttachments > 0) + { + // More than one attachment. Draw a large attachment asterisk. + GetVideoObject( &hVObject, guiAttachmentAsterisks ); + + if (uiNumAttachmentsGL > 0) + { + // Draw large yellow asterisk. + pTrav = &(hVObject->pETRLEObject[ 1 ] ); + sOffsetX += (UINT32)pTrav->usWidth; + + sNewX = ((sX + sWidth) - 6) - sOffsetX; + sNewY = sY + 4; + BltVideoObject( uiBuffer , hVObject, 1, sNewX, sNewY , VO_BLT_SRCTRANSPARENCY,NULL ); + + // reduce the number of GL attachments + uiNumAttachmentsGL--; + // Pool the remaining GLs into the general attachments + uiNumAttachmentsGeneral += uiNumAttachmentsGL; + } + else + { + // Draw large green asterisk. + pTrav = &(hVObject->pETRLEObject[ 0 ] ); + sOffsetX += (UINT32)pTrav->usWidth; + + sNewX = ((sX + sWidth) - 6) - sOffsetX; + sNewY = sY + 4; + BltVideoObject( uiBuffer , hVObject, 0, sNewX, sNewY , VO_BLT_SRCTRANSPARENCY,NULL ); + } + + uiNumAttachments--; + + // Any attachments remaining? + if (uiNumAttachments > 0) + { + for (INT16 x = 0; x < uiNumAttachments; x++) + { + // Draw small green asterisk + pTrav = &(hVObject->pETRLEObject[ 2 ] ); + sOffsetX += ((x+1) % 2) * (UINT32)pTrav->usWidth; + sOffsetY = (x % 2) * ((UINT32)pTrav->usHeight + 1); + + sNewX = ((sX + sWidth) - 6) - sOffsetX; + sNewY = sY + 4 + sOffsetY; + BltVideoObject( uiBuffer , hVObject, 2, sNewX, sNewY , VO_BLT_SRCTRANSPARENCY,NULL ); + } + } + } + } + } + /* + else if( pItem->usItemClass & IC_AMMO && pObject->ubNumberOfObjects > 1) + { + SetFontForeground( FONT_GRAY1 ); + SetFont( FONT10ARIAL ); + + UINT16 uiTotalAmmo = 0; + for (INT16 x = 0; x < pObject->ubNumberOfObjects; x++) + { + uiTotalAmmo += (*pObject)[x]->data.ubShotsLeft; + } + + swprintf( pStr, L"(%d)", uiTotalAmmo ); + + sNewX = ((sX + sWidth) - 4) - StringPixLength( pStr, FONT10ARIAL ); + sNewY = sY + 3; + + mprintf( sNewX, sNewY, pStr ); + gprintfinvalidate( sNewX, sNewY, pStr ); + + // Reset font color + SetFontForeground( FONT_MCOLOR_DKGRAY ); + } + */ +} + + BOOLEAN InItemDescriptionBox( ) { @@ -3359,6 +4632,15 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, MSYS_AddRegion( &gInvDesc); } + // HEADROCK HAM 5: Adjust Item Region + // Before creating the ammo button, we set a region that spans the entire Big Item image. Clicking on the image + // will trigger a Transformation menu. + { + MSYS_DefineRegion( &gInvDescTransformRegion, (UINT16)ITEMDESC_ITEM_X, (UINT16)ITEMDESC_ITEM_Y ,(UINT16)(ITEMDESC_ITEM_X + ITEMDESC_ITEM_WIDTH), (UINT16)(ITEMDESC_ITEM_Y + ITEMDESC_ITEM_HEIGHT), MSYS_PRIORITY_HIGHEST, + MSYS_NO_CURSOR, MSYS_NO_CALLBACK, ItemDescTransformRegionCallback ); + MSYS_AddRegion( &gInvDescTransformRegion); + } + // Add ammo eject button for GUN type objects. if ( (Item[ pObject->usItem ].usItemClass & IC_GUN) && !Item[pObject->usItem].rocketlauncher ) { @@ -3438,6 +4720,11 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, gfItemAmmoDown = FALSE; } + else + { + // Reset!! + giItemDescAmmoButton = -1; + } // HEADROCK: Tooltip Regions for stats. Only happens with Enhanced Description Box turned on. if(UsingEDBSystem() > 0) @@ -3509,6 +4796,11 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, strcpy( VObjectDesc.ImageFile, "INTERFACE\\ATTACHMENT_SLOT.STI" ); CHECKF( AddVideoObject( &VObjectDesc, &guiAttachmentSlot) ); + // HEADROCK HAM 5: Transformation Indicator + VObjectDesc.fCreateFlags = VOBJECT_CREATE_FROMFILE; + strcpy( VObjectDesc.ImageFile, "INTERFACE\\INFOBOX_Transform_Icon.STI" ); + CHECKF( AddVideoObject( &VObjectDesc, &guiTransformIconGraphic) ); + // HEADROCK: Added new STIs CHECKF( InternalInitEnhancedDescBox() ); @@ -3757,7 +5049,6 @@ BOOLEAN InternalInitItemDescriptionBox( OBJECTTYPE *pObject, INT16 sX, INT16 sY, } //CHRISL: This function is designed to recreate the attachment tooltips -extern BOOLEAN CanPlayerUseSectorInventory( SOLDIERTYPE *pSelectedSoldier ); void UpdateAttachmentTooltips(OBJECTTYPE *pObject, UINT8 ubStatusIndex) { UINT32 slotCount = 0; @@ -4452,6 +5743,8 @@ void ItemDescAttachmentsCallback( MOUSE_REGION * pRegion, INT32 iReason ) DeleteItemDescriptionBox( ); gpItemDescPrevObject = pTemp; + gpItemDescOrigAttachmentObject = pAttachment; + Object2 = *pAttachment; gfItemDescObjectIsAttachment = TRUE; InternalInitItemDescriptionBox( &Object2, gsInvDescX, gsInvDescY, 0, gpItemDescSoldier ); @@ -4816,6 +6109,27 @@ void RenderItemDescriptionBox( ) } BltVideoObjectFromIndex( guiSAVEBUFFER, guiItemGraphic, 0, sCenX, sCenY, VO_BLT_SRCTRANSPARENCY, NULL ); + // HEADROCK HAM 5: Superimpose with Transform Icon graphic + /* This bit will later be used for manual unjam via transformations. Hopefully. If not, erase it. + if (Item[gpItemDescObject->usItem].usItemClass == IC_GUN && (*gpItemDescObject)[0]->data.gun.bGunAmmoStatus < 0 ) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiTransformIconGraphic, 0, (ITEMDESC_ITEM_X+ITEMDESC_ITEM_WIDTH)-10, (ITEMDESC_ITEM_Y+ITEMDESC_ITEM_HEIGHT)-5, VO_BLT_SRCTRANSPARENCY, NULL ); + } + else*/ + { + for (UINT16 x = 0; x < MAXITEMS; x++) + { + if (Transform[x].usItem == -1) + { + break; + } + if (Transform[x].usItem == gpItemDescObject->usItem) + { + BltVideoObjectFromIndex( guiSAVEBUFFER, guiTransformIconGraphic, 0, (ITEMDESC_ITEM_X+ITEMDESC_ITEM_WIDTH)-13, (ITEMDESC_ITEM_Y+ITEMDESC_ITEM_HEIGHT)-17, VO_BLT_SRCTRANSPARENCY, NULL ); + } + } + } + // Display status DrawItemUIBarEx( gpItemDescObject, gubItemDescStatusIndex, (INT16)ITEMDESC_ITEM_STATUS_X, (INT16)ITEMDESC_ITEM_STATUS_Y, ITEMDESC_ITEM_STATUS_WIDTH, ITEMDESC_ITEM_STATUS_HEIGHT, Get16BPPColor( DESC_STATUS_BAR ), Get16BPPColor( DESC_STATUS_BAR_SHADOW ), TRUE, guiSAVEBUFFER ); @@ -5852,7 +7166,9 @@ void DeleteItemDescriptionBox( ) DeleteVideoObjectFromIndex( guiItemInfoLBEBackground ); DeleteVideoObjectFromIndex( guiMapItemDescBox ); DeleteVideoObjectFromIndex( guiAttachmentSlot ); - RenderBackpackButtons(ACTIVATE_BUTTON); /* CHRISL: Needed for new inventory backpack buttons */ + // HEADROCK HAM 5: Transform Icon + DeleteVideoObjectFromIndex( guiTransformIconGraphic ); + RenderBackpackButtons(ACTIVATE_BUTTON); // CHRISL: Needed for new inventory backpack buttons if(guiCurrentItemDescriptionScreen == SHOPKEEPER_SCREEN && gGameSettings.fOptions[TOPTION_ENHANCED_DESC_BOX]) EnableDisableShopkeeperButtons(guiCurrentItemDescriptionScreen, ACTIVATE_BUTTON); DeleteVideoObjectFromIndex( guiBullet ); @@ -5900,6 +7216,19 @@ void DeleteItemDescriptionBox( ) } } + // HEADROCK HAM 5: Remove Transform Item Region + if (&gInvDescTransformRegion) + { + MSYS_RemoveRegion( &gInvDescTransformRegion ); + if (gfItemDescTransformPopupInitialized && !gfSkipDestroyTransformPopup ) // Gotta make sure we don't destroy it when it's running! + { + delete(gItemDescTransformPopup); + gItemDescTransformPopup = NULL; + gfItemDescTransformPopupInitialized = FALSE; + gfItemDescTransformPopupVisible = FALSE; + } + } + // Remove region MSYS_RemoveRegion( &gInvDesc); @@ -5951,13 +7280,21 @@ void DeleteItemDescriptionBox( ) } } - if ( ITEM_PROS_AND_CONS( gpItemDescObject->usItem ) ) + // HEADROCK HAM 5: Instead of checking the item, check for the regions!! + //if ( ITEM_PROS_AND_CONS( gpItemDescObject->usItem ) ) + if (&(gProsAndConsRegions[0])) { MSYS_RemoveRegion( &gProsAndConsRegions[0] ); + } + if (&(gProsAndConsRegions[1])) + { MSYS_RemoveRegion( &gProsAndConsRegions[1] ); } - if(( ( Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) && !Item[gpItemDescObject->usItem].rocketlauncher ) ) + // HEADROCK HAM 5: Instead of checking the item, check for the button!! + + //if(( ( Item[ gpItemDescObject->usItem ].usItemClass & IC_GUN ) && !Item[gpItemDescObject->usItem].rocketlauncher ) ) + if (giItemDescAmmoButton > -1) { // Remove button UnloadButtonImage( giItemDescAmmoButtonImages ); @@ -6001,6 +7338,8 @@ void DeleteItemDescriptionBox( ) gfItemDescObjectIsAttachment = FALSE; gpItemDescObject = NULL; gpItemDescPrevObject = NULL; + // HEADROCK HAM 5: This stores an attachment object while we're looking at its copy. + gpItemDescOrigAttachmentObject = NULL; } @@ -8269,7 +9608,17 @@ void ItemPopupRegionCallback( MOUSE_REGION * pRegion, INT32 iReason ) RestoreExternBackgroundRect( gsItemPopupInvX, gsItemPopupInvY, gsItemPopupInvWidth, gsItemPopupInvHeight ); if ( guiCurrentItemDescriptionScreen == MAP_SCREEN ) { - MAPInternalInitItemDescriptionBox( gpItemPopupObject, (UINT8)uiItemPos, gpItemPopupSoldier ); + // HEADROCK HAM 5: Sector Inventory Item Desc Box no longer accessible during combat. + + if( gTacticalStatus.uiFlags & INCOMBAT ) + { + DoScreenIndependantMessageBox( New113HAMMessage[ 22 ], MSG_BOX_FLAG_OK, NULL ); + return; + } + else + { + MAPInternalInitItemDescriptionBox( gpItemPopupObject, (UINT8)uiItemPos, gpItemPopupSoldier ); + } } else { @@ -10629,3 +11978,632 @@ void ItemDescAdvButtonCheck( void ) DisableButton( giInvDescAdvButton[1] ); } } + +// HEADROCK HAM 5: Item Transformations callback. The user has clicked on the Big Item +// picture, possibly indicating that he wishes to transform this item into something else. +void ItemDescTransformRegionCallback( MOUSE_REGION *pRegion, INT32 reason ) +{ + if (reason == MSYS_CALLBACK_REASON_LBUTTON_UP ) + { + // A left-click triggers the Transformation Menu. The game scans memory for any Transformations + // that can be performed on this item, and creates a menu with all the options listed. + + // HEADROCK HAM 5: Disable shopkeeper item transformations entirely. + if( guiTacticalInterfaceFlags & INTERFACE_SHOPKEEP_INTERFACE ) + { + return; + } + + /////////////////////////// + // CONSTRUCT POPUP MENU + /////////////////////////// + // If we already have a popup, destroy it first. This ensures we get a fresh menu each time. + if ( gfItemDescTransformPopupInitialized ) + { + delete(gItemDescTransformPopup); + gItemDescTransformPopup = NULL; + gfItemDescTransformPopupInitialized = FALSE; + gfItemDescTransformPopupVisible = FALSE; + gfSkipDestroyTransformPopup = FALSE; + } + // create a popup + gItemDescTransformPopup = new POPUP("TRANSFORMATION MENU POPUP"); // at this point the name is used mainly for debug output + + // add a callback that lets the keyboard handler know we're done (and ready to pop up again) + gItemDescTransformPopup->setCallback(POPUP_CALLBACK_HIDE, new popupCallbackFunction( &TransformationMenuPopup_Hide ) ); + + BOOLEAN fFoundTransformations = false; + + /* + // Test the item for Gun Jams. If it's a gun and is jammed, add a Transformation Menu option to unjam it. + if ( Item[gpItemDescObject->usItem].usItemClass == IC_GUN && !EXPLOSIVE_GUN( gpItemDescObject->usItem ) ) + { + // Check ammo status + if ((*gpItemDescObject)[0]->data.gun.bGunAmmoStatus < 0) + { + // Add option + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( L"Unjam" ), new popupCallbackFunction( &TransformationMenuPopup_Unjam )); + gItemDescTransformPopup->addOption( *pOption ); + fFoundTransformations = true; + } + } + */ + + // Ammocrates may be split into magazines of any size available in the game. But not in combat. + if ( Item[gpItemDescObject->usItem].usItemClass == IC_AMMO && Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubMagType >= AMMO_BOX && !(gTacticalStatus.uiFlags & INCOMBAT) ) + { + BOOLEAN fCrateInPool = FALSE; + + // Before we continue, lets check whether our object is in the sector inventory. + // Is the sector inventory open? + if (fShowMapInventoryPool) + { + // Is our object currently in the pool? + for (UINT32 x = 0; x < pInventoryPoolList.size(); x++) + { + if (pInventoryPoolList[x].object.exists()) + { + if (&(pInventoryPoolList[x].object) == gpItemDescObject) + { + // Aha! In that case, all transformations will be done directly at the sector pool, + // with multiple results ending on the ground rather than in the inventory. + fCrateInPool = TRUE; + break; + } + } + } + } + + if (fCrateInPool) + { + for (UINT16 x = 0; x < MAXITEMS; x++) + { + if ( Item[x].usItemClass & IC_AMMO ) + { + // If this magazine has the same caliber and ammotype as the crate, has a smaller size, and is + // not an ammo crate itself... + if ( Magazine[Item[x].ubClassIndex].ubCalibre == Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre && + Magazine[Item[x].ubClassIndex].ubAmmoType == Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubAmmoType && + Magazine[Item[x].ubClassIndex].ubMagSize < Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubMagSize && + Magazine[Item[x].ubClassIndex].ubMagType < AMMO_BOX ) + { + UINT16 usMagSize = Magazine[Item[x].ubClassIndex].ubMagSize; + + CHAR16 MenuRowText[300]; + swprintf( MenuRowText, gzTransformationMessage[ 7 ], usMagSize ); + // Generate a new option for the menu + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( TransformationMenuPopup_SplitCrate, x ) ); + // Add the option to the menu. + gItemDescTransformPopup->addOption( *pOption ); + // Set this flag so we know we have at least one Transformation available. + fFoundTransformations = true; + } + } + } + } + else + { + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( gzTransformationMessage[ 6 ] ), new popupCallbackFunction( TransformationMenuPopup_SplitCrateInInventory ) ); + gItemDescTransformPopup->addOption( *pOption ); + fFoundTransformations = true; + } + } + + // Scan the Transformation list for the current item. Create pop-up options as required. + INT32 iTransformIndex = -1; + for (INT32 x = 0; x < MAXITEMS; x++) + { + if (Transform[x].usItem == -1) + { + break; + } + if (Transform[x].usItem == gpItemDescObject->usItem) + { + iTransformIndex++; + + CHAR16 MenuRowText[300]; + if ( Transform[x].usAPCost > 0 && gTacticalStatus.uiFlags & INCOMBAT && gTacticalStatus.uiFlags & TURNBASED ) + { + swprintf (MenuRowText, L"%s (%d AP)", Transform[x].szMenuRowText, Transform[x].usAPCost ); + } + else + { + swprintf (MenuRowText, Transform[x].szMenuRowText); + } + + // Generate a new option for the menu + POPUP_OPTION *pOption = new POPUP_OPTION(&std::wstring( MenuRowText ), new popupCallbackFunction( &TransformationMenuPopup_Transform, &Transform[x] ) ); + // Set the function that tests whether it's valid at the moment. + pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_TestValid, &Transform[x] )); + // Add the option to the menu. + gItemDescTransformPopup->addOption( *pOption ); + // Set this flag so we know we have at least one Transformation available. + fFoundTransformations = true; + } + } + if (!fFoundTransformations) + { + POPUP_OPTION * pOption = new POPUP_OPTION( &std::wstring( gzTransformationMessage[ 0 ] ), new popupCallbackFunction( &TransformationMenuPopup_Transform, NULL ) ); + pOption->setAvail(new popupCallbackFunction( &TransformationMenuPopup_TestValid, NULL )); + gItemDescTransformPopup->addOption( *pOption ); + } + + UINT16 usPosX = (UINT16)((ITEMDESC_ITEM_X + (ITEMDESC_ITEM_X + ITEMDESC_ITEM_WIDTH)) / 2); + UINT16 usPosY = (UINT16)((ITEMDESC_ITEM_Y + (ITEMDESC_ITEM_Y + ITEMDESC_ITEM_HEIGHT)) / 2); + if (pRegion->MouseXPos > ITEMDESC_ITEM_X && pRegion->MouseXPos < (ITEMDESC_ITEM_X + ITEMDESC_ITEM_WIDTH)) + { + usPosX = pRegion->MouseXPos; + } + if (pRegion->MouseYPos > ITEMDESC_ITEM_Y && pRegion->MouseYPos < (ITEMDESC_ITEM_Y + ITEMDESC_ITEM_HEIGHT)) + { + usPosY = pRegion->MouseYPos; + } + gItemDescTransformPopup->setPosition( usPosX, usPosY ); + + gfItemDescTransformPopupInitialized = TRUE; + gfItemDescTransformPopupVisible = TRUE; + + // In compliance with current bugs, if there's a bullet icon, hide it to prevent overlaps. + if (giItemDescAmmoButton > -1) + { + HideButton(giItemDescAmmoButton); + } + + gItemDescTransformPopup->show(); + + // Now that the popup is initialized, lets set the help text for each line. Note that we have to do it here + // (rather than before) because only now are the MOUSE_REGIONs ready to receive help text at all!! + INT32 iNumOptions = 0; + for (INT32 x = 0; x < MAXITEMS; x++) + { + if (Transform[x].usItem == -1) + { + break; + } + if (Transform[x].usItem == gpItemDescObject->usItem) + { + SetRegionFastHelpText( &(gItemDescTransformPopup->MenuRegion[iNumOptions]), Transform[x].szTooltipText ); + iNumOptions++; + } + } + + } + else if (reason == MSYS_CALLBACK_REASON_RBUTTON_UP ) + { + // Behave like the background region, closing the box. + OBJECTTYPE *pTemp = gpItemDescPrevObject; + DeleteItemDescriptionBox( ); + if (pTemp != NULL) + { + InternalInitItemDescriptionBox( pTemp, gsInvDescX, gsInvDescY, 0, gpItemDescSoldier ); + } + } +} + +////////////////////////////////////////////////////////////////////// +// HEADROCK HAM 5: Callback Functions for the Transformation Menu +////////////////////////////////////////////////////////////////////// + +// This function handles hiding the menu. +void TransformationMenuPopup_Hide(void) +{ + // If an Eject Ammo button exists for the current DB item, then restore it to view. + if (giItemDescAmmoButton > -1) + { + ShowButton(giItemDescAmmoButton); + } + + // Signal the renderer to stop drawing this menu. + gfItemDescTransformPopupVisible = FALSE; + fMapPanelDirty = TRUE; +} + +// This function handles callback when one of the options on the Transformation menu is clicked. +// This function takes care of all activity that is not directly related to the transformation data, the transformed +// object, or the soldier performing the transformation. +void TransformationMenuPopup_Transform( TransformInfoStruct * Transform) +{ + // If the item is in a stack, ask for confirmation. + if (gpItemDescObject->ubNumberOfObjects > 1) + { + //Ask for confirmation + gTransformInProgress = Transform; + guiTransformInProgressPrevScreen = guiCurrentScreen; + CHAR16 pStr[500]; + swprintf( pStr, gzTransformationMessage[ 5 ], gpItemDescObject->ubNumberOfObjects ); + DoScreenIndependantMessageBox( pStr, MSG_BOX_FLAG_YESNO, ConfirmTransformationMessageBoxCallBack ); + } + else + { + TransformFromItemDescBox( Transform ); + } +} + +BOOLEAN TransformationMenuPopup_TestValid(TransformInfoStruct * Transform) +{ + if (Transform == NULL) + { + return false; + } + else + { + UINT16 usAPCost = Transform->usAPCost; + INT32 iBPCost = Transform->iBPCost; + + if (EnoughPoints( gpItemDescSoldier, usAPCost, iBPCost, false )) + { + return true; + } + else + { + return false; + } + } +} + +// HEADROCK HAM 5: This Transformation Menu Callback will attempt to unjam a jammed gun. If the +// soldier lacks APs to do so, it will fail with a screen message. Otherwise the gun is unjammed, +// causing the normal effects. +void TransformationMenuPopup_Unjam() +{ + /* + if ( Item[gpItemDescObject->usItem].usItemClass != IC_GUN || EXPLOSIVE_GUN( gpItemDescObject->usItem ) || (*gpItemDescObject)[0]->data.gun.bGunAmmoStatus > 0) + { + AssertMsg( 0, "Transformation Menu allowed us to attempt to unjam an unjammed gun. This is illegal!" ); + return; + } + + if(EnoughPoints(gpItemDescSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM], FALSE)) + { + DeductPoints(gpItemDescSoldier, APBPConstants[AP_UNJAM], APBPConstants[BP_UNJAM]); + INT8 bChanceMod; + + if ( Weapon[gpItemDescObject->usItem].EasyUnjam ) + bChanceMod = 100; + else + bChanceMod = (INT8) (GetReliability( gpItemDescObject )* 4); + + int iResult = SkillCheck( gpItemDescSoldier, UNJAM_GUN_CHECK, bChanceMod); + + if (iResult > 0) + { + // yay! unjammed the gun + (*gpItemDescObject)[0]->data.gun.bGunAmmoStatus *= -1; + + // MECHANICAL/DEXTERITY GAIN: Unjammed a gun + + if (bChanceMod < 100) // don't give exp for unjamming an easily unjammable gun + { + StatChange( gpItemDescSoldier, MECHANAMT, 5, FALSE ); + StatChange( gpItemDescSoldier, DEXTAMT, 5, FALSE ); + } + + RenderItemDescriptionBox(); + + return; + } + } + else + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%s does not have enough APs to unjam this weapon.", gpItemDescSoldier->name); + } + */ +} + +// HEADROCK HAM 5: This is a handler for transforming an ammo crate into magazines. We get the itemnumber of the +// magazine to create, and proceed to create one magazine after the other. They are placed either into the +// carrying soldier's inventory, or into the sector inventory if it is there. +void TransformationMenuPopup_SplitCrate( UINT16 usMagazineItem ) +{ + if (gItemDescTransformPopup != NULL && gfItemDescTransformPopupInitialized == TRUE) + { + gItemDescTransformPopup->hide(); + } + + OBJECTTYPE MagazineObject; + UINT16 usShotsLeft = (*gpItemDescObject)[gubItemDescStatusIndex]->data.ubShotsLeft; + UINT16 usMagazineSize = Magazine[Item[usMagazineItem].ubClassIndex].ubMagSize; + + UINT8 ubAmmoType = Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubAmmoType; + UINT8 ubCaliber = Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre; + + // Simple. Just create as many clips as you can, drop them all to the sector inventory. + UINT32 uiNumMagazinesToCreate = (usShotsLeft / usMagazineSize) + ((usShotsLeft % usMagazineSize) > 0); + + for (UINT32 x = 0; x < uiNumMagazinesToCreate; x++) + { + UINT16 usBulletsInMag = __min( usShotsLeft, usMagazineSize ); + + MagazineObject.initialize(); + CreateAmmo(usMagazineItem, &MagazineObject, usBulletsInMag); + AutoPlaceObjectToWorld( gpItemDescSoldier, &MagazineObject, true ); + + if (&MagazineObject != NULL) + { + DeleteObj( &MagazineObject ); + } + + usShotsLeft -= usBulletsInMag; + } + + CHAR16 pStr[500]; + swprintf( pStr, gzTransformationMessage[ 8 ], Item[gpItemDescObject->usItem].szItemName, uiNumMagazinesToCreate, usMagazineSize ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, pStr ); + + OBJECTTYPE *gTempCrate = gpItemDescObject; + + gfSkipDestroyTransformPopup = TRUE; + DeleteItemDescriptionBox(); + gfSkipDestroyTransformPopup = FALSE; + + gTempCrate->RemoveObjectAtIndex(gubItemDescStatusIndex); +} + +void TransformationMenuPopup_SplitCrateInInventory( ) +{ + if (gItemDescTransformPopup != NULL && gfItemDescTransformPopupInitialized == TRUE) + { + gItemDescTransformPopup->hide(); + } + + OBJECTTYPE MagazineObject; + UINT16 usShotsLeft = (*gpItemDescObject)[gubItemDescStatusIndex]->data.ubShotsLeft; + UINT16 usOrigShotsLeft = usShotsLeft; + + UINT8 ubAmmoType = Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubAmmoType; + UINT8 ubCaliber = Magazine[Item[gpItemDescObject->usItem].ubClassIndex].ubCalibre; + + // Drop into the soldier's inventory. + if (gpItemDescSoldier == NULL) + { + return; + } + for (INT16 sPocket = BIGPOCK1POS; sPocket < NUM_INV_SLOTS && usShotsLeft > 0; sPocket++) + { + // To cut down on processing time, skip any pockets containing an item that will not appear on the + // possible splits list. + BOOLEAN fEmptyPocketOk = FALSE; + BOOLEAN fMagazineInPocketOk = FALSE; + BOOLEAN fHalfEmptyMagazineInPocketOk = FALSE; //MM: changed this so that half empty magazines will be topped off. TODO: eventually apply similar logic to when a crate is clicked on a gun as well. + + UINT16 usMagazineToCreate = 0; + + OBJECTTYPE *pObjInPocket = &(gpItemDescSoldier->inv[sPocket]); + if ( pObjInPocket->exists() == true ) + { + if (Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubAmmoType == ubAmmoType && + Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubCalibre == ubCaliber && + Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubMagType < AMMO_BOX) + { + fMagazineInPocketOk = TRUE; + usMagazineToCreate = pObjInPocket->usItem; + for (INT8 cnt = 0; cnt < pObjInPocket->ubNumberOfObjects; ++cnt) + { + if ((*pObjInPocket)[cnt]->data.ubShotsLeft < Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubMagSize) + { + fHalfEmptyMagazineInPocketOk = TRUE; + break; + } + } + } + } + else + { + fEmptyPocketOk = TRUE; + } + + if (fMagazineInPocketOk) + { + UINT8 ubCapacity = ItemSlotLimit(pObjInPocket, sPocket, gpItemDescSoldier); + ubCapacity -= pObjInPocket->ubNumberOfObjects; + + for ( UINT16 x = 0; x < ubCapacity && usShotsLeft > 0; x++) + { + UINT16 usBulletsInMag = __min( usShotsLeft, Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubMagSize ); + + MagazineObject.initialize(); + CreateAmmo(usMagazineToCreate, &MagazineObject, usBulletsInMag); + PlaceObject( gpItemDescSoldier, (INT8)sPocket, &MagazineObject ); + + if (&MagazineObject != NULL) + { + DeleteObj( &MagazineObject ); + } + + usShotsLeft -= usBulletsInMag; + } + + if (fHalfEmptyMagazineInPocketOk && usShotsLeft > 0) + { + for (INT8 cnt = 0; cnt < pObjInPocket->ubNumberOfObjects ;++cnt) + { + if ((*pObjInPocket)[cnt]->data.ubShotsLeft < Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubMagSize) + { + UINT16 bulletsToAdd = Magazine[Item[pObjInPocket->usItem].ubClassIndex].ubMagSize - (*pObjInPocket)[cnt]->data.ubShotsLeft; + + if ( usShotsLeft < bulletsToAdd ) + bulletsToAdd = usShotsLeft; + + (*pObjInPocket)[cnt]->data.ubShotsLeft += bulletsToAdd; + usShotsLeft -= bulletsToAdd; + } + } + } + } + else if (fEmptyPocketOk) + { + // Check whether any magazine of any size can fit into this slot. Find the type that can fit the + // most rounds in per slot. + + UINT8 ubBestStackCapacity = 0; + UINT32 uiBestRoundCapacity = 0; + UINT16 usBestItem = 0; + + for (UINT16 x = 0; x < MAXITEMS; x++) + { + if (Item[x].usItemClass & IC_AMMO) + { + if (Magazine[Item[x].ubClassIndex].ubAmmoType == ubAmmoType && + Magazine[Item[x].ubClassIndex].ubCalibre == ubCaliber && + Magazine[Item[x].ubClassIndex].ubMagType < AMMO_BOX ) + { + UINT16 usTempMagazineSize = Magazine[Item[x].ubClassIndex].ubMagSize; + OBJECTTYPE TempMagObject; + TempMagObject.initialize(); + CreateAmmo(x, &TempMagObject, usTempMagazineSize); + + UINT8 ubCapacity = ItemSlotLimit(&TempMagObject, sPocket, gpItemDescSoldier); + + if ((UINT32)(ubCapacity * usTempMagazineSize) > uiBestRoundCapacity) + { + uiBestRoundCapacity = ubCapacity * usTempMagazineSize; + ubBestStackCapacity = ubCapacity; + usBestItem = x; + } + + DeleteObj( &TempMagObject ); + } + } + } + + if (usBestItem > 0) + { + UINT16 usMagazineSize = Magazine[Item[usBestItem].ubClassIndex].ubMagSize; + for (UINT16 y = 0; y < ubBestStackCapacity && usShotsLeft > 0; y++) + { + UINT16 usBulletsInMag = __min( usShotsLeft, usMagazineSize ); + + MagazineObject.initialize(); + CreateAmmo( usBestItem, &MagazineObject, usBulletsInMag ); + PlaceObject( gpItemDescSoldier, (INT8)sPocket, &MagazineObject ); + + if (&MagazineObject != NULL) + { + DeleteObj( &MagazineObject ); + } + + usShotsLeft -= usBulletsInMag; + } + } + } + } + + if (usOrigShotsLeft > usShotsLeft) + { + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, gzTransformationMessage[ 9 ], Item[gpItemDescObject->usItem].szItemName, gpItemDescSoldier->name ); + } + else + { + // Until we determine whether a box would be better... + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, gzTransformationMessage[ 10 ], gpItemDescSoldier->name ); + //CHAR16 pStr[300]; + //swprintf( pStr, gzTransformationMessage[ 10 ], gpItemDescSoldier->name ); + //DoScreenIndependantMessageBox( pStr, MSG_BOX_FLAG_OK, NULL ); + } + + if (usShotsLeft > 0) + { + (*gpItemDescObject)[gubItemDescStatusIndex]->data.ubShotsLeft = usShotsLeft; + RenderItemDescriptionBox(); + } + else + { + OBJECTTYPE *gTempCrate = gpItemDescObject; + + gfSkipDestroyTransformPopup = TRUE; + DeleteItemDescriptionBox(); + gfSkipDestroyTransformPopup = FALSE; + + gTempCrate->RemoveObjectAtIndex(gubItemDescStatusIndex); + //DeleteObj( gTempCrate ); + } +} + + +void TransformFromItemDescBox( TransformInfoStruct * Transform) +{ + + // Hide the Transform Popup menu, we don't need it anymore. It will be destroyed and rebuilt next time we click the + // Transformation region anyway. + if (gItemDescTransformPopup != NULL && gfItemDescTransformPopupInitialized == TRUE) + { + gItemDescTransformPopup->hide(); + } + + // Record the item's original class. If it's a gun item, we may need to manually delete the + // eject ammo button! + UINT32 uiOrigClass = Item[gpItemDescObject->usItem].usItemClass; + BOOLEAN fWasAttachment = gfItemDescObjectIsAttachment; + + // Carry out the transformation on this item, using the data we've received. + gpItemDescObject->TransformObject( gpItemDescSoldier, gubItemDescStatusIndex, Transform, gpItemDescPrevObject ); + + // Check to see if we need to manually erase the ammo button. + UINT32 uiNewClass = Item[gpItemDescObject->usItem].usItemClass; + BOOLEAN fEraseAmmoButton = FALSE; + if (uiOrigClass & IC_GUN && !(uiNewClass & IC_GUN)) + { + fEraseAmmoButton = TRUE; + } + + // We're going to shut down and reinitialize the DescBox now, to ensure that we get the DescBox for the resulting + // item instead of the old one. + + // Save previous ItemDesc-related parameters, before deleting the box (they get erased...) + OBJECTTYPE *pTemp = gpItemDescObject; + OBJECTTYPE *pTempParent = NULL; + BOOLEAN fTempIsAttachment = FALSE; + OBJECTTYPE *pTempAttachment = NULL; + // If the object was attached and still is, we'll need to save the attachment details now before we destroy the + // description box. That way we can reopen it with all the data intact. + if (fWasAttachment && gfItemDescObjectIsAttachment) + { + pTempParent = gpItemDescPrevObject; + fTempIsAttachment = gfItemDescObjectIsAttachment; + pTempAttachment = gpItemDescOrigAttachmentObject; + } + + // This flag tells the deletion function not to destroy the Transform Popup Menu - we need it to stay alive through + // this. + gfSkipDestroyTransformPopup = TRUE; + + // DELETE THE BOX! + DeleteItemDescriptionBox( ); + + /*if (fEraseAmmoButton) + { + // Transformation from gun to non-gun. Erase ammo button manually. + UnloadButtonImage( giItemDescAmmoButtonImages ); + RemoveButton( giItemDescAmmoButton ); + }*/ + + // Unflag. Next closure of the DescBox will destroy the menu as normal. + gfSkipDestroyTransformPopup = FALSE; + + if (pTemp->usItem > 0 && // Open only if the item is still valid. + !(fWasAttachment && !fTempIsAttachment)) // Do not reopen the box if the item was an attachment and has been removed from its gun. + { + // Restore previous settings + gpItemDescObject = pTemp; + gpItemDescPrevObject = pTempParent; + gfItemDescObjectIsAttachment = fTempIsAttachment; + gpItemDescOrigAttachmentObject = pTempAttachment; + + // RESTART THE BOX! + InternalInitItemDescriptionBox( gpItemDescObject, gsInvDescX, gsInvDescY, 0, gpItemDescSoldier ); + } +} + +// This is a callback function for the box that asks you whether or not you want to transform all items +// in a stack. +void ConfirmTransformationMessageBoxCallBack( UINT8 bExitValue ) +{ + if( bExitValue == MSG_BOX_RETURN_YES ) + { + UINT32 iTempScreen = guiCurrentScreen; + guiCurrentScreen = guiTransformInProgressPrevScreen; + TransformFromItemDescBox( gTransformInProgress ); + guiCurrentScreen = iTempScreen; + guiTransformInProgressPrevScreen = 0; + } +} \ No newline at end of file diff --git a/Tactical/Interface Items.h b/Tactical/Interface Items.h index 123c604b..1ba19a92 100644 --- a/Tactical/Interface Items.h +++ b/Tactical/Interface Items.h @@ -169,6 +169,19 @@ public: OBJECTTYPE ItemPointerInfo; }; +// HEADROCK HAM 5: Enums for big-item display attachment asterisks. +enum +{ + ATTACHMENT_NONE, + ATTACHMENT_GENERAL, + ATTACHMENT_GL, + ATTACHMENT_RECOILREDUCTION, + ATTACHMENT_OPTICAL, +}; + +// HEADROCK HAM 5: Asterisks for use with big item images +extern UINT32 guiAttachmentAsterisks; + // Itempickup stuff BOOLEAN InitializeItemPickupMenu( SOLDIERTYPE *pSoldier, INT32 sGridNo, ITEM_POOL *pItemPool, INT16 sScreenX, INT16 sScreenY, INT8 bZLevel ); void RenderItemPickupMenu( ); @@ -211,6 +224,8 @@ BOOLEAN HandleCompatibleAmmoUI( SOLDIERTYPE *pSoldier, INT8 bInvPos, BOOLEAN fOn void RenderBulletIcon(OBJECTTYPE *pObject, UINT32 ubStatusIndex = 0); void INVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObject, INT16 sX, INT16 sY, INT16 sWidth, INT16 sHeight, UINT8 fDirtyLevel, UINT8 *pubHighlightCounter, UINT8 ubStatusIndex, BOOLEAN fOutline, INT16 sOutlineColor, UINT8 iter = 0 ); +// HEADROCK HAM 5: Drawing a large item pic with extra data. +void MAPINVRenderItem( UINT32 uiBuffer, SOLDIERTYPE * pSoldier, OBJECTTYPE *pObject, UINT32 uiItemGraphicNum, INT16 sX, INT16 sY, INT16 sWidth, INT16 sHeight, BOOLEAN fOutline, INT16 sOutlineColor ); // CHRISL: Add a new function that will be used to render a pocket silhouette void INVRenderSilhouette( UINT32 uiBugger, INT16 PocketIndex, INT16 SilIndex, INT16 sX, INT16 sY, INT16 sWideth, INT16 sHeight); // CHRISL: New function to handle display of inventory quantities based on item current in cursor @@ -313,7 +328,36 @@ extern INT32 giActiveAttachmentPopup; // the attachment popup to display in maps extern POPUP* gEquipPopups[NUM_INV_SLOTS]; // popup list for attachment slots extern INT32 giActiveEquipPopup; // the attachment popup to display in mapscreen +// HEADROCK HAM 5: Item Transformation Popup +extern POPUP* gItemDescTransformPopup; +extern BOOLEAN gfItemDescTransformPopupInitialized; +extern BOOLEAN gfItemDescTransformPopupVisible; + // BOB : duh... void DoAttachment( UINT8 subObject, INT32 iItemPos ); +// THE_BOB (AKA BOB): ammo box/mag popups +void PocketPopupFull( SOLDIERTYPE *pSoldier, INT16 sPocket ); // displays all suported options +void PocketPopupDefault( SOLDIERTYPE *pSoldier, INT16 sPocket ); // displays only pocket-specific (predefined) options + +POPUP * createPopupForPocket( SOLDIERTYPE *pSoldier, INT16 sPocket ); // creates a popup positioned next to the pocket + +// THE_BOB +extern UINT16 gsPocketUnderCursor; + + +void addArmorToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addLBEToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addWeaponsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addWeaponGroupsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addBombsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addFaceGearToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + +void addRifleGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addRocketAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addMiscToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); +void addKitsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + #endif diff --git a/Tactical/Interface Panels.cpp b/Tactical/Interface Panels.cpp index 88834777..cca65df9 100644 --- a/Tactical/Interface Panels.cpp +++ b/Tactical/Interface Panels.cpp @@ -2531,7 +2531,15 @@ void RenderSMPanel( BOOLEAN *pfDirty ) } } - RenderItemDescriptionBox( ); + // HEADROCK HAM 5: Test for active Transform Popup + if (gfItemDescTransformPopupVisible) + { + gItemDescTransformPopup->show(); + } + else + { + RenderItemDescriptionBox( ); + } } else { diff --git a/Tactical/Interface Utils.cpp b/Tactical/Interface Utils.cpp index 8d7a9cd3..e55f1eb6 100644 --- a/Tactical/Interface Utils.cpp +++ b/Tactical/Interface Utils.cpp @@ -545,4 +545,16 @@ void RenderSoldierFace( SOLDIERTYPE *pSoldier, INT16 sFaceX, INT16 sFaceY, BOOLE } +// HEADROCK HAM 5: This function draws a rectangle around the item image in the zoomed inventory +void DrawItemOutlineZoomedInventory( INT16 sX, INT16 sY, INT16 sWidth, INT16 sHeight, INT16 sColor, UINT32 uiBuffer ) +{ + UINT32 uiDestPitchBYTES; + UINT8 *pDestBuf; + pDestBuf = LockVideoSurface( uiBuffer, &uiDestPitchBYTES ); + SetClippingRegionAndImageWidth( uiDestPitchBYTES, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT ); + + RectangleDraw( TRUE, sX, sY, sWidth, sHeight, sColor, pDestBuf ); + + UnLockVideoSurface( uiBuffer ); +} diff --git a/Tactical/Interface Utils.h b/Tactical/Interface Utils.h index 04e2c2dc..bcb422f3 100644 --- a/Tactical/Interface Utils.h +++ b/Tactical/Interface Utils.h @@ -23,5 +23,8 @@ BOOLEAN LoadCarPortraitValues( void ); // get rid of the loaded portraits for cars void UnLoadCarPortraits( void ); +// HEADROCK HAM 5: Helper for zoomed inventory +void DrawItemOutlineZoomedInventory( INT16 sX, INT16 sY, INT16 sWidth, INT16 sHeight, INT16 sColor, UINT32 uiBuffer ); + #endif \ No newline at end of file diff --git a/Tactical/Item Types.cpp b/Tactical/Item Types.cpp index 0a969e33..5af1599e 100644 --- a/Tactical/Item Types.cpp +++ b/Tactical/Item Types.cpp @@ -128,6 +128,7 @@ void DestroyLBE(OBJECTTYPE* pObj) void MoveItemsInSlotsToLBE( SOLDIERTYPE *pSoldier, std::vector& LBESlots, LBENODE* pLBE, OBJECTTYPE* pObj) { + for(unsigned int i=0; iinv[LBESlots[i]].exists() == false) // No item in this pocket diff --git a/Tactical/Item Types.h b/Tactical/Item Types.h index c33e8261..e8e85a67 100644 --- a/Tactical/Item Types.h +++ b/Tactical/Item Types.h @@ -4,6 +4,9 @@ #include "types.h" #include #include +// THE_BOB : added for pocket popup definitions +#include +#include "popup_definition.h" //if the number of slots are ever changed, the loading / saving checksum should use this value to make conversion easier #define NUM_ORIGINAL_INV_SLOTS 19 @@ -264,6 +267,20 @@ extern std::list LBEArray; #define OLD_MAX_ATTACHMENTS_101 4 #define OLD_MAX_OBJECTS_PER_SLOT_101 8 +// HEADROCK HAM 5: Define Self-Transformations +// Have to define this early on, because this struct is used in OBJECTTYPE +#define MAX_NUM_TRANSFORMATION_RESULTS 10 + +typedef struct +{ + UINT16 usItem; + UINT16 usResult[MAX_NUM_TRANSFORMATION_RESULTS]; + UINT16 usAPCost; + INT32 iBPCost; + CHAR16 szMenuRowText[50]; + CHAR16 szTooltipText[300]; +} TransformInfoStruct; + namespace Version101 { //union was originally unnamed @@ -526,6 +543,8 @@ public: BOOLEAN AttachObjectOAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttachment, BOOLEAN playSound = TRUE, UINT8 subObject = 0); BOOLEAN AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttachment, BOOLEAN playSound = TRUE, UINT8 subObject = 0, INT32 iItemPos = -1, BOOLEAN fRemoveProhibited = TRUE, std::vector usAttachmentSlotIndexVector = std::vector()); BOOLEAN RemoveAttachment( OBJECTTYPE* pAttachment, OBJECTTYPE * pNewObj = NULL, UINT8 subObject = 0, SOLDIERTYPE * pSoldier = NULL, BOOLEAN fForceInseperable = FALSE, BOOLEAN fRemoveProhibited = TRUE); + // HEADROCK HAM 5: Object Transformation + BOOLEAN TransformObject( SOLDIERTYPE * pSoldier, UINT8 ubStatusIndex, TransformInfoStruct * Transform, OBJECTTYPE *pParent ); //see comments in .cpp static void DeleteMe(OBJECTTYPE** ppObject); @@ -590,6 +609,17 @@ extern OBJECTTYPE gTempObject; #define IC_BOBBY_GUN ( IC_GUN | IC_LAUNCHER ) #define IC_BOBBY_MISC ( IC_GRENADE | IC_BOMB | IC_MISC | IC_MEDKIT | IC_KIT | IC_BLADE | IC_THROWING_KNIFE | IC_PUNCH | IC_FACE | IC_LBEGEAR ) +// HEADROCK HAM 5: Inventory Filter Types +#define IC_MAPFILTER_GUN ( IC_GUN | IC_LAUNCHER ) +#define IC_MAPFILTER_AMMO ( IC_AMMO ) +#define IC_MAPFILTER_EXPLOSV ( IC_GRENADE | IC_BOMB ) +#define IC_MAPFILTER_MELEE ( IC_BLADE | IC_PUNCH | IC_THROWN | IC_THROWING_KNIFE ) +#define IC_MAPFILTER_KIT ( IC_KIT | IC_MEDKIT | IC_APPLIABLE ) +#define IC_MAPFILTER_LBE ( IC_LBEGEAR | IC_BELTCLIP ) +#define IC_MAPFILTER_ARMOR ( IC_ARMOUR | IC_FACE ) +#define IC_MAPFILTER_MISC ( IC_TENTACLES | IC_KEY | IC_MISC | IC_MONEY | IC_NONE ) +#define IC_MAPFILTER_ALL ( IC_MAPFILTER_GUN | IC_MAPFILTER_AMMO | IC_MAPFILTER_EXPLOSV | IC_MAPFILTER_MELEE | IC_MAPFILTER_KIT | IC_MAPFILTER_LBE | IC_MAPFILTER_ARMOR | IC_MAPFILTER_MISC ) + // Chrisl: Define attachment classes #define AC_DEFAULT1 0x00000001 //1 #define AC_BARREL 0x00000002 //2 @@ -904,6 +934,10 @@ public: }; #define SIZEOF_POCKETTYPE offsetof( POCKETTYPE, POD ) extern std::vector LBEPocketType; + +// THE_BOB : added for pocket popup definitions +extern std::map LBEPocketPopup; + typedef enum ePOCKET_TYPE { NO_POCKET_TYPE = 0, @@ -1445,6 +1479,9 @@ typedef struct UINT32 uiIndex; } ComboMergeInfoStruct; +// HEADROCK HAM 5: Defining this here because we need MAXITEMS. The struct is defined earlier. +extern TransformInfoStruct Transform[MAXITEMS+1]; + extern ComboMergeInfoStruct AttachmentComboMerge[MAXITEMS+1]; BOOLEAN EXPLOSIVE_GUN(UINT16 x ); diff --git a/Tactical/Items.cpp b/Tactical/Items.cpp index 6a173948..5d57aa31 100644 --- a/Tactical/Items.cpp +++ b/Tactical/Items.cpp @@ -55,6 +55,9 @@ #include "debug control.h" #include "math.h" + // THE_BOB : added for pocket popup definitions + #include + #include "popup_definition.h" #endif #ifdef JA2UB @@ -72,9 +75,19 @@ class SOLDIERTYPE; void SetNewItem( SOLDIERTYPE *pSoldier, UINT8 ubInvPos, BOOLEAN fNewItem ); extern SOLDIERTYPE *gpItemDescSoldier; +// HEADROCK HAM 5: We need access to these values for item transformation purposes +extern BOOLEAN gfItemDescObjectIsAttachment; +extern OBJECTTYPE *gpItemDescObject; +extern OBJECTTYPE *gpItemDescPrevObject; +extern OBJECTTYPE *gpItemDescOrigAttachmentObject; +extern OBJECTTYPE gCloneItemDescObject; extern BOOLEAN fShowMapInventoryPool; extern UINT32 guiCurrentItemDescriptionScreen; extern BOOLEAN AutoPlaceObjectInInventoryStash( OBJECTTYPE *pItemPtr, INT32 sGridNo ); +// HEADROCK HAM 5: Also need these to trigger Map Inventory changes appropriately. +extern BOOLEAN fMapInventoryZoom; +// HEADROCK HAM 5: And this, for checking whether an item is in the pool. +extern std::vector pInventoryPoolList; UINT16 OldWayOfCalculatingScopeBonus(SOLDIERTYPE *pSoldier); // weight units are 100g each @@ -1154,6 +1167,8 @@ ComboMergeInfoStruct AttachmentComboMerge[MAXITEMS+1];// = // {NOTHING, {NOTHING, NOTHING}, NOTHING }, //}; +// HEADROCK HAM 5: Item Transformations table, containing all possible Transformations. +TransformInfoStruct Transform[MAXITEMS+1]; UINT16 IncompatibleAttachments[MAXATTACHMENTS][2];// = //{ @@ -1223,6 +1238,9 @@ std::vector LoadBearingEquipment; std::vector LBEPocketType; //TODO make the indices of this a define, because I do not know what is a large pocket (I guess 3) +// THE_BOB : added for pocket popup definitions +std::map LBEPocketPopup; + //POCKETTYPE LBEPocketType[MAXITEMS+1]; //= //{ // { /* Blank Entry */ 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} }, @@ -4761,7 +4779,8 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac //WarmSteel - Replaced this with one that also checks default attachments, otherwise you could not replace built-in bonuses with default inseperable attachments. //RemoveProhibitedAttachments(pSoldier, this, usResult); - ReInitMergedItem(pSoldier, this, usOldItem); + // HEADROCK HAM 5: Added argument for statusindex. + ReInitMergedItem(pSoldier, this, usOldItem, 0); //AutoPlaceObject( pAttachment ); //ADB ubWeight has been removed, see comments in OBJECTTYPE @@ -4866,7 +4885,7 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac //WarmSteel - Replaced this with one that also checks default attachments, otherwise you could not replace built-in bonuses with default inseperable attachments. //RemoveProhibitedAttachments(pSoldier, this, usResult); - ReInitMergedItem(pSoldier, this, usOldItem); + ReInitMergedItem(pSoldier, this, usOldItem, 0); } break; @@ -4923,7 +4942,8 @@ BOOLEAN OBJECTTYPE::AttachObjectNAS( SOLDIERTYPE * pSoldier, OBJECTTYPE * pAttac //WarmSteel - Replaced this with one that also checks default attachments, otherwise you could not replace built-in bonuses with default inseperable attachments. //RemoveProhibitedAttachments(pSoldier, this, usResult); - ReInitMergedItem(pSoldier, this, usOldItem); + // HEADROCK HAM 5: Added argument for statusindex. + ReInitMergedItem(pSoldier, this, usOldItem, subObject); if ( ubType != TREAT_ARMOUR ) { @@ -5296,7 +5316,9 @@ void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 //WarmSteel - Needed for merges, because the new items don't get their new default attachments. //This does the same as RemoveProhibitedAttachments but is a bit more thorough at it. //This is at the risk that items move around if more than one slot is valid, but since this is used for "new" guns after merges, that's acceptable. -void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem){ +// HEADROCK HAM 5: Added argument for statusindex. +void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem, UINT8 ubStatusIndex) +{ attachmentList tempAttachList; attachmentList tempSlotChangingAttachList; @@ -5304,18 +5326,18 @@ void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem) UINT8 slotCount; std::vector usAttachmentSlotIndexVector = GetItemSlots(pObj); - if( !(*pObj)[0]->attachments.empty() ){ + if( !(*pObj)[ubStatusIndex]->attachments.empty() ){ //Have to take all attachments off, because of possible incompatibilities with the default attachments (can't NOT attach a default attachment because some stupid item already attached is incompatible with it). //Delete all default inseperable attachments from the old gun. //This is safe UNLESS the inseparable default attachments is not the only attachment of the same kind on the gun. //I can't think of any reason why this would happen, and if it does the worst that can happen is your attachment disappearing. - for(slotCount = 0; slotCount < (*pObj)[0]->attachments.size(); slotCount++){ - UINT16 usAttach = (*pObj)[0]->GetAttachmentAtIndex(slotCount)->usItem; + for(slotCount = 0; slotCount < (*pObj)[ubStatusIndex]->attachments.size(); slotCount++){ + UINT16 usAttach = (*pObj)[ubStatusIndex]->GetAttachmentAtIndex(slotCount)->usItem; if(Item[usAttach].inseparable){ for(UINT16 cnt = 0; cnt < MAX_DEFAULT_ATTACHMENTS && Item[usOldItem].defaultattachments[cnt] != 0; cnt++){ if(Item[usOldItem].defaultattachments[cnt] == usAttach){ - (*pObj)[0]->RemoveAttachmentAtIndex(slotCount); + (*pObj)[ubStatusIndex]->RemoveAttachmentAtIndex(slotCount); break; } } @@ -5323,36 +5345,38 @@ void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem) } slotCount = 0; //Put all other attachments into a temporary storage, so we can try to re-attach later. - for(attachmentList::iterator iter = (*pObj)[0]->attachments.begin(); iter != (*pObj)[0]->attachments.end(); iter++, slotCount++) { + for(attachmentList::iterator iter = (*pObj)[ubStatusIndex]->attachments.begin(); iter != (*pObj)[ubStatusIndex]->attachments.end(); iter++, slotCount++) { if(iter->exists()){ UINT16 cnt = 0; tempAttachList.push_back((*iter)); } } } + //clear the attachment list, we've saved the attachments somewhere safe now. - (*pObj)[0]->attachments.clear(); + (*pObj)[ubStatusIndex]->attachments.clear(); //Make sure the attachment slot data is correct. //std::vector tempItemSlots = GetItemSlots(pObj); - (*pObj)[0]->attachments.resize(usAttachmentSlotIndexVector.size()); + (*pObj)[ubStatusIndex]->attachments.resize(usAttachmentSlotIndexVector.size()); //pObj->usAttachmentSlotIndexVector = tempItemSlots; + //Now add all default attachments, but add them with the same status as the gun. We don't want to make repairing guns easy :) for(UINT16 cnt = 0; cnt < MAX_DEFAULT_ATTACHMENTS && Item[pObj->usItem].defaultattachments[cnt] != 0; cnt++){ //Only add inseparable default attachments, because they are likely "part" of the gun. if(Item[Item[pObj->usItem].defaultattachments[cnt]].inseparable){ static OBJECTTYPE defaultAttachment; - CreateItem(Item [ pObj->usItem ].defaultattachments[cnt],(*pObj)[0]->data.objectStatus,&defaultAttachment); - AssertMsg(pObj->AttachObject(NULL,&defaultAttachment, FALSE, 0, -1, FALSE), "A default attachment could not be attached after merging, this should not be possible."); + CreateItem(Item [ pObj->usItem ].defaultattachments[cnt],(*pObj)[ubStatusIndex]->data.objectStatus,&defaultAttachment); + AssertMsg(pObj->AttachObject(NULL,&defaultAttachment, FALSE, ubStatusIndex, -1, FALSE), "A default attachment could not be attached after merging, this should not be possible."); } } //First re-attach any slot-changing attachments. for (attachmentList::iterator iter = tempSlotChangingAttachList.begin(); iter != tempSlotChangingAttachList.end();) { - if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE )){ + if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex )){ //This seems to be rather valid. Can't be 100% sure though. - if(pObj->AttachObject(NULL, &(*iter), FALSE)){ + if(pObj->AttachObject(NULL, &(*iter), FALSE, ubStatusIndex)){ //Ok now we can be sure, lets remove this object so we don't try to drop it later. iter = tempSlotChangingAttachList.erase(iter); } else { @@ -5365,9 +5389,9 @@ void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem) //Time to re-attach the other attachments, if we can. I am the king of copy pasta. for (attachmentList::iterator iter = tempAttachList.begin(); iter != tempAttachList.end();) { - if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE)){ + if( ValidItemAttachmentSlot(pObj, iter->usItem, TRUE, FALSE, ubStatusIndex)){ //This seems to be rather valid. Can't be 100% sure though. - if(pObj->AttachObject(NULL, &(*iter), FALSE)){ + if(pObj->AttachObject(NULL, &(*iter), FALSE, ubStatusIndex)){ //Ok now we can be sure, lets remove this object so we don't try to drop it later. iter = tempAttachList.erase(iter); } else { @@ -5385,7 +5409,9 @@ void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem) if (pSoldier) { if ( !AutoPlaceObject( pSoldier, &(*iter), FALSE ) ) { // put it on the ground - AddItemToPool( pSoldier->sGridNo, &(*iter), 1, pSoldier->pathing.bLevel, 0 , -1 ); + // HEADROCK HAM 5: A much more suitable function. Works in both tactical and mapscreen! + AutoPlaceObjectToWorld( pSoldier, &(*iter), true ); + //AddItemToPool( pSoldier->sGridNo, &(*iter), 1, pSoldier->pathing.bLevel, 0 , -1 ); } } } @@ -5397,11 +5423,14 @@ void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem) if (pSoldier) { if ( !AutoPlaceObject( pSoldier, &(*iter), FALSE ) ) { // put it on the ground - AddItemToPool( pSoldier->sGridNo, &(*iter), 1, pSoldier->pathing.bLevel, 0 , -1 ); + // HEADROCK HAM 5: A much more suitable function. Works in both tactical and mapscreen! + AutoPlaceObjectToWorld( pSoldier, &(*iter), true ); + //AddItemToPool( pSoldier->sGridNo, &(*iter), 1, pSoldier->pathing.bLevel, 0 , -1 ); } } } } + } void EjectAmmoAndPlace(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT8 subObject) @@ -12597,3 +12626,339 @@ FLOAT GetItemCooldownFactor( OBJECTTYPE * pObj ) return cooldownfactor; } + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// HEADROCK HAM 5: Item Transformation. +// Item Transformation is a new way to interact with items. Where Merges combine two items into one, a Transformation +// can turn one item into another item, or into more than one item. +// To instigate a Transformation, the player uses a menu to choose the desired effect. Then the program comes here to +// see how it's done. +// The instructions for performing a specific transformation are drawn from XML and fed into this function as a pointer. +// They tell us what items will result from this transformation, as well as the APBP cost. + +BOOLEAN OBJECTTYPE::TransformObject( SOLDIERTYPE * pSoldier, UINT8 ubStatusIndex, TransformInfoStruct * Transform, OBJECTTYPE *pParent ) +{ + // The argument "Transform" is a pointer to an entry in the Transformation Data array. By looking at the pointer we + // can determine all the data we need. Therefore, this pointer must not be null! + AssertMsg( Transform != NULL, String( "OBJECTTYPE::TransformObject attempt with invalid Transformation data." ) ); + + // Booleans to track what happened during the transformation. + BOOLEAN fSplit = FALSE; + BOOLEAN fDropped = FALSE; + BOOLEAN fRemoved = FALSE; + BOOLEAN fItemInPool = FALSE; + + // This boolean tracks whether we've managed to place an item in a soldier's inventory. + BOOLEAN fFoundPlaceInInventory = FALSE; + + // Constants for storing details about the original item. + OBJECTTYPE TempItem; + UINT16 usOrigItem = this->usItem; + UINT32 uiOrigClass = Item[this->usItem].usItemClass; + UINT8 ubOrigNumObjects = this->ubNumberOfObjects; + + UINT16 usAPCost = 0; + INT32 iBPCost = 0; + + // An array to store all result items. + UINT16 usResult[MAX_NUM_TRANSFORMATION_RESULTS]; + UINT32 iNumResults = 0; + + // Start reading transformation data with APBP costs. + usAPCost = Transform->usAPCost; + iBPCost = Transform->iBPCost; + + // Check whether our soldier can afford this transformation! + if (!EnoughPoints( pSoldier, (INT16)usAPCost, iBPCost, true )) + { + return false; + } + else + { + // Soldier can afford the transformation. Deduct APBP as necessary. + DeductPoints( pSoldier, (INT16)usAPCost, iBPCost, false ); + } + + // Read all result items for this transformation. Record them into an array. + for (UINT32 curResult = 0; curResult < MAX_NUM_TRANSFORMATION_RESULTS; curResult++) + { + if (Transform->usResult[curResult] > 0) + { + usResult[curResult] = Transform->usResult[curResult]; + // Count the number of valid result items + iNumResults++; + } + } + + // If there are no result items, the Transformation is illegal - we need at least one resulting item! + AssertMsg( iNumResults, String( "OBJECTTYPE::TransformObject attempting to transform an object which has no resulting items defined!" ) ); + + if (iNumResults > 1) + { + fSplit = TRUE; + } + + // Play a gun-cocking sound, it's the best one we've got ATM. + PlayJA2Sample( ATTACH_TO_GUN, RATE_11025, SoundVolume( MIDVOLUME, pSoldier->sGridNo ), 1, SoundDir( pSoldier->sGridNo ) ); + + // Before we continue, lets check whether our object is in the sector inventory. + // Is the sector inventory open? + if (fShowMapInventoryPool) + { + // Is our object currently in the pool? + for (UINT32 x = 0; x < pInventoryPoolList.size(); x++) + { + if (pInventoryPoolList[x].object.exists()) + { + if (&(pInventoryPoolList[x].object) == this) + { + // Aha! In that case, all transformations will be done directly at the sector pool, + // with multiple results ending on the ground rather than in the inventory. + fItemInPool = TRUE; + break; + } + } + } + } + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // The Transformation itself + //////////////////////////////////////////////////////////////////////////////////////////////////// + + // We're going to do the transformation in steps, going hierarchially down from the item's own attachments, + // to itsself, then its parents, and finally resulting items and unrelated inventory. + // Note that we'll need to change the item's usItem property several times during this process, so lets + // record its original usItem now. + UINT16 usOldItem = this->usItem; + + // STEP 1: Attachments and subsidiary objects. + + // First of all, if the original item is a gun, we'll need to see if it can hold the same magazine after the + // transformation. If it can't, the magazine will be ejected immediately into the soldier's inventory. + + // Is this a gun, AND does it have any ammo loaded? + if ( Item[this->usItem].usItemClass == IC_GUN && (*this)[ubStatusIndex]->data.gun.usGunAmmoItem != NONE && (*this)[ubStatusIndex]->data.gun.ubGunShotsLeft > 0 ) + { + // Is the resulting item not a gun, OR is the resulting item of a different caliber, OR is the resulting item's + // magazine too small? + if ( Item[usResult[0]].usItemClass != IC_GUN || Weapon[Item[usResult[0]].ubClassIndex].ubCalibre != Weapon[Item[this->usItem].ubClassIndex].ubCalibre || (*this)[ubStatusIndex]->data.gun.ubGunShotsLeft > Weapon[Item[usResult[0]].ubClassIndex].ubMagSize ) + { + // item types/calibers/magazines don't match, spit out old ammo + EjectAmmoAndPlace(pSoldier, this); + } + } + + // STEP 2: Check the item's attachments to see whether they still fit. + // We'll have to fool this function into thinking we've already changed the item. + // Note that this function automatically drops invalid attachments to the ground. + this->usItem = usResult[0]; + // Repeat for each object in the stack. + for ( UINT8 x = 0; x < this->ubNumberOfObjects; x++ ) + { + ReInitMergedItem(pSoldier, this, usOldItem, x); + } + this->usItem = usOldItem; + + //RemoveProhibitedAttachments(pSoldier, this, usOldItem); + + /////////////////////////////////////////////////////////////// + // TRANSFORM + this->usItem = usResult[0]; + // Record the new itemclass + UINT32 uiNewClass = Item[this->usItem].usItemClass; + // Make a clone of this object, so that we can point the DescBox to it later if we run into any problems. + gCloneItemDescObject = *(this); + /////////////////////////////////////////////////////////////// + + //////////////////// + // Placement checks + + // Is the item an attachment? If so, check its parent to see if it's still compatible. + if (pParent != NULL) + { + // When transforming an attachment, "THIS" is actually a copy of the object, called Object2. So transforming + // "this" would only yield, at most, a superficial change (Description Box will change). The attachment item + // itself will not change at all. + + // Lets change the attachment item directly, then. + gpItemDescOrigAttachmentObject->usItem = usResult[0]; + // Make a copy of it, so we know what data it contained. + OBJECTTYPE pClone = *gpItemDescOrigAttachmentObject; + + // Test the parent now. See whether all attachments are still valid on it. + ReInitMergedItem(pSoldier, pParent, pParent->usItem, ubStatusIndex); + + gpItemDescOrigAttachmentObject = NULL; + + // After reiniting the attachments on the parent, "this" still exists, but the actual object is gone. + // Lets look through the parent's attachments list, and see if we can find it again. + UINT8 ubNewAttachmentsIndex = 0; + for(attachmentList::iterator iter = (*pParent)[ubStatusIndex]->attachments.begin(); iter != (*pParent)[ubStatusIndex]->attachments.end(); iter++, ubNewAttachmentsIndex++) + { + if(iter->exists()) + { + // Compare it to the clone we made earlier. + if (*(iter) == pClone) + { + // Yes, here it is! + gpItemDescOrigAttachmentObject = (*pParent)[ubStatusIndex]->GetAttachmentAtIndex( ubNewAttachmentsIndex ); + break; + } + } + } + + // Have we failed to find our item on the reinited parent? + if (gpItemDescOrigAttachmentObject == NULL) + { + // It has been removed. Lets reset all the Description Box extra variables. + // Note that by doing this, we actually trigger closing the description box later down the line. + pParent = NULL; + gfItemDescObjectIsAttachment = NULL; + gpItemDescPrevObject = NULL; + fRemoved = TRUE; + } + } + + // Our object is not an attachment. If it is on a soldier, it may still not fit in the pocket where + // it is currently placed, so lets see if we have to move it. + else if (!fItemInPool) + { + for (INT8 bPocket = HELMETPOS; bPocket < NUM_INV_SLOTS; bPocket++) + { + if (&(pSoldier->inv[bPocket]) == this) + { + // Found our item. Does it fit in this slot? + if (!CanItemFitInPosition(pSoldier, this, bPocket, FALSE) ) + { + if (!AutoPlaceObject( pSoldier, this, TRUE )) + { + fDropped = TRUE; + + AutoPlaceObjectToWorld( pSoldier, this, TRUE ); + + //Unfortunately the above function will not erase the item in tactical mode, so lets + //double-check. + if (pSoldier->inv[bPocket].exists() ) + { + DeleteObj( &(pSoldier->inv[bPocket]) ); + } + } + // Whatever we've done with it, THIS is no longer a valid item, so lets + // fool the desc box by switching it with the clone. + gpItemDescObject = &gCloneItemDescObject; + } + break; + } + } + } + + // If either the new or old items were LBEs, lets check the entire LBE inventory for item-size compatibility! + //if (uiOrigClass & IC_LBEGEAR || uiNewClass & IC_LBEGEAR ) + if (!fItemInPool) + { + for (INT8 bPocket = HELMETPOS; bPocket < NUM_INV_SLOTS; bPocket++) + { + if (pSoldier->inv[bPocket].exists()) + { + // Found an item. Does it still fit inside its own slot? + if (!CanItemFitInPosition(pSoldier, &(pSoldier->inv[bPocket]), bPocket, FALSE) ) + { + if (!AutoPlaceObject( pSoldier, &(pSoldier->inv[bPocket]), TRUE )) + { + fDropped = TRUE; + + AutoPlaceObjectToWorld( pSoldier, &(pSoldier->inv[bPocket]), TRUE ); + + //Unfortunately the above function will not erase the item in tactical mode, so lets + //double-check. + if (pSoldier->inv[bPocket].exists() ) + { + DeleteObj( &(pSoldier->inv[bPocket]) ); + } + } + } + } + } + } + + ////////////////////////////// + // Multiple Results + // + // If the item has several transformation results defined, that means we're going to split it into two or + // more items. + + // We actually start with any results above the first. This occurs if the item is split into two or more other + // items. We'll try to place them in the pSoldier's inventory if possible, otherwise they are dumped to the + // sector pool. + + // Iterate through the results array we've constructed earlier. + for (UINT32 x = 1; x < iNumResults; x++) + { + for (UINT32 y = 0; y < ubOrigNumObjects; y++) + { + // Create the result item. Set its condition to match that of the original. + CreateItem( usResult[x], (*this)[ubStatusIndex]->data.objectStatus, &gTempObject ); + + if (!fItemInPool) + { + // Try placing it in the soldier's invnetory. + if (!AutoPlaceObject( pSoldier, &gTempObject, TRUE )) + { + // Failed to find a place in the inventory. Dump to sector pool. + AutoPlaceObjectToWorld( pSoldier, &gTempObject, true ); + + fDropped = TRUE; + } + } + else + { + AutoPlaceObjectToWorld( pSoldier, &gTempObject, true ); + } + + // Cleanup after the autoplace has to occur in tactical mode. + if (gTempObject.exists()) + { + DeleteObj( &(gTempObject) ); + } + } + } + + // Check the soldier to see how his stats have changed as a result of altering his gear. + ApplyEquipmentBonuses(pSoldier); + + if (fItemInPool && fSplit) + { + CHAR16 pStr[500]; + // Item was split apart. Since it was in the sector inventory, it's common sense that all results + // are in the sector inventory as well, so no need to report anything extra. + swprintf( pStr, gzTransformationMessage[ 1 ], Item[usOrigItem].szItemName, pSoldier->name ); + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, pStr ); + } + else if (fSplit || fDropped) + { + CHAR16 pStr[500]; + if (fSplit && !fDropped) + { + // Item was split apart, but all subitems remained in the inventory. + swprintf( pStr, gzTransformationMessage[ 2 ], Item[usOrigItem].szItemName, pSoldier->name ); + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, pStr ); + } + else if (fDropped && !fSplit) + { + // Either the item itself or another item has been dropped to the sector inventory due to lack of + // space. + swprintf( pStr, gzTransformationMessage[ 3 ], pSoldier->name ); + DoScreenIndependantMessageBox( pStr, MSG_BOX_FLAG_OK, NULL ); + } + else if (fDropped && fSplit) + { + // Item was split apart. Either the item itself or another item has been dropped to the sector + // inventory due to lack of space. + swprintf( pStr, gzTransformationMessage[ 4 ], Item[usOrigItem].szItemName, pSoldier->name ); + DoScreenIndependantMessageBox( pStr, MSG_BOX_FLAG_OK, NULL ); + } + } + + // Signal a successful transformation. + return TRUE; +} \ No newline at end of file diff --git a/Tactical/Items.h b/Tactical/Items.h index d8f00945..43849251 100644 --- a/Tactical/Items.h +++ b/Tactical/Items.h @@ -111,7 +111,8 @@ void RemoveInvObject( SOLDIERTYPE *pSoldier, UINT16 usItem ); void InitItemAttachments(OBJECTTYPE* pObj); std::vector GetItemSlots(OBJECTTYPE* pObj, UINT8 subObject = 0, BOOLEAN fAttachment = FALSE); void RemoveProhibitedAttachments(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usItem, BOOLEAN fOnlyRemoveWhenSlotsChange = 1); -void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem); +// HEADROCK HAM 5: Added argument for statusindex. +void ReInitMergedItem(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT16 usOldItem, UINT8 ubStatusIndex); void EjectAmmoAndPlace(SOLDIERTYPE* pSoldier, OBJECTTYPE* pObj, UINT8 subObject = 0); BOOLEAN CanItemFitInVehicle( SOLDIERTYPE *pSoldier, OBJECTTYPE *pObj, INT8 bPos, BOOLEAN fDoingPlacement ); diff --git a/Tactical/LOS.cpp b/Tactical/LOS.cpp index 4dcaeec9..d0f9316e 100644 --- a/Tactical/LOS.cpp +++ b/Tactical/LOS.cpp @@ -2339,6 +2339,18 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend BOOLEAN fCanSpewBlood = FALSE; INT8 bSpewBloodLevel; + // HEADROCK HAM 5: Fragments read attacking weapon from explosive, not from firer. Theoretically, + // all bullets should read this value... + UINT16 usAttackingWeapon = 0; + if (pBullet->fFragment) + { + usAttackingWeapon = pBullet->fromItem; + } + else + { + usAttackingWeapon = pFirer->usAttackingWeapon; + } + // structure IDs for mercs match their merc IDs pTarget = MercPtrs[ pStructure->usStructureID ]; @@ -2370,11 +2382,28 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend ubAmmoType = AMMO_KNIFE; } + else if (pBullet->fFragment) + { + // Read ammo type from the explosive data. + ubAmmoType = Explosive[Item[pBullet->fromItem].ubClassIndex].ubFragType; + } else { ubAmmoType = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType; } + // HEADROCK HAM 5.1: This is an utter hack, but it may be necessary. This soldier is hit by a fragment, + // so he needs to have his animations be interruptible. This takes care of soldiers hitting themselves + // with their own fragments. + if (pBullet->fFragment && pTarget == pBullet->pFirer) + { + if (pTarget->usAniCode >1) + { + pTarget->flags.fInNonintAnim = FALSE; + pTarget->flags.fRTInNonintAnim = FALSE; + } + } + // at least partly compensate for "near miss" increases for this guy, after all, the bullet // actually hit him! // take this out for now at least... no longer certain that he was awarded a suppression pt @@ -2397,7 +2426,8 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend // adult monster types have a weak spot if ( (pTarget->ubBodyType >= ADULTFEMALEMONSTER) && (pTarget->ubBodyType <= YAM_MONSTER) ) { - ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo ); + // HEADROCK HAM 5.1: Bullet data now contains its original coordinates. + ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->sOrigGridNo, pTarget->sGridNo ); if ( ubAttackDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] ) { // may hit weak spot! @@ -2485,7 +2515,8 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend { UINT8 ubOppositeDirection; - ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->pFirer->sGridNo, pTarget->sGridNo ); + // HEADROCK HAM 5.1: Bullet data now contains its original GridNo + ubAttackDirection = (UINT8) GetDirectionToGridNoFromGridNo( pBullet->sOrigGridNo, pTarget->sGridNo ); ubOppositeDirection = gOppositeDirection[ ubAttackDirection ]; if ( ! ( ubOppositeDirection == pTarget->ubDirection || ubAttackDirection == gOneCCDirection[ pTarget->ubDirection ] || ubAttackDirection == gOneCDirection[ pTarget->ubDirection ] ) ) @@ -2514,7 +2545,8 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend } // Determine damage, checking guy's armour, etc - sRange = GetRangeInCellCoordsFromGridNoDiff( pFirer->sGridNo, pTarget->sGridNo ); + sRange = GetRangeInCellCoordsFromGridNoDiff( pBullet->sOrigGridNo, pTarget->sGridNo ); + if ( gTacticalStatus.uiFlags & GODMODE && !(pFirer->flags.uiStatusFlags & SOLDIER_PC)) { // in god mode, and firer is computer controlled @@ -2541,9 +2573,11 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend // shouldn't happen but iImpact = 0; } - iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); + // HEADROCK HAM 5: Added argument + iDamage = BulletImpact( pFirer, pBullet, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); // handle hit here... - if( ( pFirer->bTeam == 0 ) ) + // HEADROCK HAM 5: Fragments not counted as shots. + if( ( pFirer->bTeam == 0 ) && !(pBullet->fFragment) ) { // SANDRO - new mercs' records // if we shoot with buckshot or similar, do not count a hit for every pellet @@ -2619,7 +2653,8 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend // shouldn't happen but iImpact = 0; } - iDamage = BulletImpact( pFirer, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); + // HEADROCK HAM 5: Added argument + iDamage = BulletImpact( pFirer, pBullet, pTarget, ubHitLocation, iImpact, sHitBy, &ubSpecial ); // accidentally shot pTarget->flags.fIntendedTarget = FALSE; @@ -2695,11 +2730,13 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend memset( &(SWeaponHit), 0, sizeof( SWeaponHit ) ); SWeaponHit.usSoldierID = pTarget->ubID; SWeaponHit.uiUniqueId = pTarget->uiUniqueSoldierIdValue; - SWeaponHit.usWeaponIndex = pFirer->usAttackingWeapon; + // HEADROCK HAM 5: Already defined. + SWeaponHit.usWeaponIndex = usAttackingWeapon; SWeaponHit.sDamage = (INT16) iDamage; // breath loss is based on original impact of bullet SWeaponHit.sBreathLoss = (INT16) ( (iImpact * APBPConstants[BP_GET_WOUNDED] * (pTarget->bBreathMax * 100 - pTarget->sBreathRed)) / 10000 ); - SWeaponHit.usDirection = GetDirectionFromGridNo( pFirer->sGridNo, pTarget ); + // HEADROCK HAM 5.1: Bullet data contains the correct original GridNo + SWeaponHit.usDirection = GetDirectionFromGridNo( pBullet->sOrigGridNo, pTarget ); SWeaponHit.sXPos = (INT16)pTarget->dXPos; SWeaponHit.sYPos = (INT16)pTarget->dYPos; SWeaponHit.sZPos = 20; @@ -2717,7 +2754,8 @@ BOOLEAN BulletHitMerc( BULLET * pBullet, STRUCTURE * pStructure, BOOLEAN fIntend SWeaponHit.ubSpecial = ubSpecial; // now check to see if the bullet goes THROUGH this person! (not vehicles) - if ( !(pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE) && (AmmoTypes[ubAmmoType].canGoThrough) && !EXPLOSIVE_GUN( pFirer->usAttackingWeapon ) ) + // HEADROCK HAM 5: Attacking weapon already defined above. + if ( !(pTarget->flags.uiStatusFlags & SOLDIER_VEHICLE) && (AmmoTypes[ubAmmoType].canGoThrough) && !EXPLOSIVE_GUN( usAttackingWeapon ) ) { // if we do more damage than expected, then the bullet will be more likely // to be lodged in the body @@ -2826,7 +2864,8 @@ void BulletHitStructure( BULLET * pBullet, UINT16 usStructureID, INT32 iImpact, SStructureHit.sXPos = (INT16) FIXEDPT_TO_INT32( qCurrX + FloatToFixed( 0.5f ) ); // + 0.5); SStructureHit.sYPos = (INT16) FIXEDPT_TO_INT32( qCurrY + FloatToFixed( 0.5f ) ); // (dCurrY + 0.5); SStructureHit.sZPos = CONVERT_HEIGHTUNITS_TO_PIXELS( (INT16) FIXEDPT_TO_INT32( qCurrZ + FloatToFixed( 0.5f )) );// dCurrZ + 0.5) ); - SStructureHit.usWeaponIndex = pFirer->usAttackingWeapon; + // HEADROCK HAM 5.1: Bullet already contains this data... + SStructureHit.usWeaponIndex = pBullet->fromItem; SStructureHit.bWeaponStatus = pBullet->ubItemStatus; SStructureHit.ubAttackerID = pFirer->ubID; SStructureHit.usStructureID = usStructureID; @@ -2938,6 +2977,16 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure // returns remaining impact amount + // HEADROCK HAM 5.1: Define differently for fragments + UINT8 ubAmmoType = 0; + if (pBullet->fFragment) + { + ubAmmoType = Explosive[Item[pBullet->fromItem].ubClassIndex].ubFragType; + } + else + { + ubAmmoType = pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType; + } INT32 iCurrImpact; INT32 iImpactReduction; @@ -2953,7 +3002,7 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure //else if ( pBullet->usFlags & BULLET_FLAG_SMALL_MISSILE ) //{ // stops if using HE ammo - else if ( AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].highExplosive && !AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].antiTank ) + else if ( AmmoTypes[ubAmmoType].highExplosive && !AmmoTypes[ubAmmoType].antiTank ) { *pfHit = TRUE; return( 0 ); @@ -2962,14 +3011,15 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure // ATE: Alrighty, check for shooting door locks... // First check this is a type of struct that can handle locks... - if ( pStructure->fFlags & ( STRUCTURE_DOOR | STRUCTURE_OPENABLE ) && PythSpacesAway( (INT16) pBullet->sTargetGridNo, pStructure->sGridNo ) <= 2 ) + // HEADROCK HAM 5.1: Fragments have radical target gridnos, which would prevent them from opening a lock otherwise. + if ( pStructure->fFlags & ( STRUCTURE_DOOR | STRUCTURE_OPENABLE ) && (PythSpacesAway( (INT16) pBullet->sTargetGridNo, pStructure->sGridNo ) <= 2 || pBullet->fFragment) ) { // lookup lock table to see if we have a lock, // and then remove lock if enough damage done.... pDoor = FindDoorInfoAtGridNo( pBullet->sGridNo ); // Does it have a lock? - INT16 lockBustingPower = AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].lockBustingPower; + INT16 lockBustingPower = AmmoTypes[ubAmmoType].lockBustingPower; // WANNE: bugfix: No door returned, so the game crashes! if (pDoor) @@ -2981,7 +3031,13 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure // Yup..... // Chance that it hit the lock.... - if ( PreRandom( 2 ) == 0 || lockBustingPower > 0 ) + // HEADROCK HAM 5.1: Fragments are numerous, so reduce their chance of hitting the lock. + UINT8 ubRandomChanceHitLock = 2; + if (pBullet->fFragment) + { + ubRandomChanceHitLock = 10; + } + if ( PreRandom( ubRandomChanceHitLock ) == 0 || lockBustingPower > 0 ) { // Adjust damage-- CC adjust this based on gun type, etc..... //sLockDamage = (INT16)( 35 + Random( 35 ) ); @@ -3009,7 +3065,15 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure RemoveDoorInfoFromTable( pDoor->sGridNo ); // MARKSMANSHIP GAIN (marksPts): Opened/Damaged a door - StatChange( pBullet->pFirer, MARKAMT, 10, FALSE ); + // HEADROCK HAM 5.1: Fragments don't increase marksmanship, but can increase Explosives! + if (pBullet->fFragment == FALSE) + { + StatChange( pBullet->pFirer, MARKAMT, 10, FALSE ); + } + else + { + StatChange( pBullet->pFirer, EXPLODEAMT, 3, FALSE ); + } // SANDRO - merc records - locks breached if ( pBullet->pFirer->ubProfile != NO_PROFILE ) @@ -3036,7 +3100,7 @@ INT32 HandleBulletStructureInteraction( BULLET * pBullet, STRUCTURE * pStructure iImpactReduction = gubMaterialArmour[ pStructure->pDBStructureRef->pDBStructure->ubArmour ]; iImpactReduction = StructureResistanceIncreasedByRange( iImpactReduction, pBullet->iRange, pBullet->iLoop ); - iImpactReduction = (INT32) (iImpactReduction * AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType].structureImpactReductionDivisor)); + iImpactReduction = (INT32) (iImpactReduction * AmmoTypes[ubAmmoType].structureImpactReductionMultiplier / max(1,AmmoTypes[ubAmmoType].structureImpactReductionDivisor)); //switch (pBullet->pFirer->inv[ pBullet->pFirer->ubAttackingHand ][0]->data.gun.ubGunAmmoType) //{ @@ -3986,17 +4050,19 @@ INT8 FireBullet( SOLDIERTYPE * pFirer, BULLET * pBullet, BOOLEAN fFake ) { pBullet->usClockTicksPerUpdate = 30; } + // HEADROCK HAM 5: Bomb Fragments + else if (pBullet->fFragment) + { + pBullet->usClockTicksPerUpdate = 30; + } else { - OBJECTTYPE* pObjAttHand = pFirer->GetUsedWeapon( &pFirer->inv[pFirer->ubAttackingHand] ); - UINT16 usItemUsed = pFirer->GetUsedWeaponNumber( &pFirer->inv[pFirer->ubAttackingHand] ); - //afp-start //always a fast bullet if ( pBullet->usFlags & ( BULLET_FLAG_CREATURE_SPIT | BULLET_FLAG_KNIFE | BULLET_FLAG_MISSILE | BULLET_FLAG_SMALL_MISSILE | BULLET_FLAG_TANK_CANNON | BULLET_FLAG_FLAME /*| BULLET_FLAG_TRACER*/ ) ) - pBullet->usClockTicksPerUpdate = (Weapon[ usItemUsed ].ubBulletSpeed + GetBulletSpeedBonus( pObjAttHand ) ) / 10; + pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10; else if (gGameSettings.fOptions[TOPTION_ALTERNATE_BULLET_GRAPHICS]) - pBullet->usClockTicksPerUpdate = (Weapon[ usItemUsed ].ubBulletSpeed + GetBulletSpeedBonus( pObjAttHand ) ) / 10; + pBullet->usClockTicksPerUpdate = (Weapon[ pFirer->usAttackingWeapon ].ubBulletSpeed + GetBulletSpeedBonus(&pFirer->inv[pFirer->ubAttackingHand]) ) / 10; else pBullet->usClockTicksPerUpdate = 1; //afp-end @@ -4441,6 +4507,8 @@ INT8 FireBulletGivenTargetNCTH( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, pBullet->iImpact = ubImpact; pBullet->iRange = GunRange( pObjAttHand, pFirer ); // SANDRO - added argument + // HEADROCK HAM 5.1: Define original point. + pBullet->sOrigGridNo = ((INT32)dStartX) / CELL_X_SIZE + ((INT32)dStartY) / CELL_Y_SIZE * WORLD_COLS; pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS; pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pFirer->sGridNo ].sHeight ) ); @@ -4941,6 +5009,8 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA pBullet->iImpact = ubImpact; pBullet->iRange = GunRange( pObjAttHand, pFirer ); // SANDRO - added argument + // HEADROCK HAM 5.1: Define original point. + pBullet->sOrigGridNo = ((INT32)dStartX) / CELL_X_SIZE + ((INT32)dStartY) / CELL_Y_SIZE * WORLD_COLS; pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS; pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ - CONVERT_PIXELS_TO_HEIGHTUNITS( gpWorldLevelData[ pFirer->sGridNo ].sHeight ) ); @@ -5009,6 +5079,175 @@ INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOA return( TRUE ); } +// HEADROCK HAM 5: This function is similar to FireBulletGivenTargetNCTH above, except it is used for firing fragments +// out of an explosion. The method is the same: a bullet is created and propelled towards an X/Y/Z location. The difference +// is that there is no firing weapon - the bullet needs to receive details from the explosive item, and supply the +// thrower's pointer instead of the "shooter"'s, to ensure proper experience distribution and hostility checks go to the +// thrower. +// Note that this function does not make provisions for Fake Firing. There is no need for it, and it would needlessly +// complicate things anyway. +// Also note we receive start coordinates from the bomb itself, because there's nowhere else to get them. +INT8 FireFragmentGivenTarget( SOLDIERTYPE * pThrower, FLOAT dStartX, FLOAT dStartY, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usExplosiveItem ) +{ + // Artificial... + dStartZ++; + + FLOAT d2DDistance=0; + FLOAT dDeltaX=0; + FLOAT dDeltaY=0; + FLOAT dDeltaZ=0; + + DOUBLE ddOrigHorizAngle=0; + DOUBLE ddOrigVerticAngle=0; + DOUBLE ddHorizAngle=0; + DOUBLE ddVerticAngle=0; + DOUBLE ddAdjustedHorizAngle=0; + DOUBLE ddAdjustedVerticAngle=0; + DOUBLE ddDummyHorizAngle=0; + DOUBLE ddDummyVerticAngle=0; + + BULLET * pBullet=NULL; + INT32 iBullet=0; + + INT32 iDistance=0; + + UINT8 ubLoop=0; + UINT8 ubShots=0; + UINT8 ubImpact=0; + UINT16 usBulletFlags = 0; + int n=0; + + //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTarget")); + + dDeltaX = dEndX - dStartX; + dDeltaY = dEndY - dStartY; + dDeltaZ = dEndZ - dStartZ; + + //lal bugfix + if( dDeltaZ > 0 ) + d2DDistance = Distance3D( dDeltaX, dDeltaY, dDeltaZ ); + else + d2DDistance = Distance2D( dDeltaX, dDeltaY ); + + iDistance = (INT32) d2DDistance; + + if ( d2DDistance != iDistance ) + { + iDistance += 1; + d2DDistance = (FLOAT) ( iDistance); + } + + ddOrigHorizAngle = atan2( dDeltaY, dDeltaX ); + ddOrigVerticAngle = atan2( dDeltaZ, (d2DDistance * 2.56f) ); + ddAdjustedHorizAngle = ddOrigHorizAngle; + ddAdjustedVerticAngle = ddOrigVerticAngle; + + ubShots = 1; + fTracer = FALSE; + + // HEADROCK HAM B2.5: Set tracer effect on/off for individual bullets in a Tracer Magazine, as part of the + // New Tracer System. + + ubImpact =(UINT8) Explosive[Item[usExplosiveItem].ubClassIndex].ubFragDamage; + + iBullet = CreateBullet( pThrower->ubID, false, usBulletFlags, usExplosiveItem ); + if (iBullet == -1) + { + //DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("Failed to create bullet") ); + + return( FALSE ); + } + pBullet = GetBulletPtr( iBullet ); + + pBullet->fFragment = true; + + // Set HitBy to 1, these are completely unaimed after all. + pBullet->sHitBy = 100; + + // HEADROCK HAM 4: TODO: Determine whether this value is required anymore. Why ever let bullets + // pass through the roof at all?? + if (dStartZ < WALL_HEIGHT_UNITS) + { + if (dEndZ > WALL_HEIGHT_UNITS) + { + pBullet->fCheckForRoof = TRUE; + } + else + { + pBullet->fCheckForRoof = FALSE; + } + } + else // dStartZ >= WALL_HEIGHT_UNITS; presumably > + { + if (dEndZ < WALL_HEIGHT_UNITS) + { + pBullet->fCheckForRoof = TRUE; + } + else + { + pBullet->fCheckForRoof = FALSE; + } + } + + // CHRISL: If we don't set the ddHorizAngle, at the very least, shooting by corners is impossible. Unfortunately, I don't know what other + // impacts these two lines will have. Headrock didn't include them when he originally wrote NCTH but they are in a similar location in the + // OCTH code. Hopefully no issues will result from this change. + ddHorizAngle = ddOrigHorizAngle; + ddVerticAngle = ddOrigVerticAngle; + // HEADROCK HAM 4: Firing increments no longer required here (NCTH) + // calculate by hand (well, without angles) to match LOS + pBullet->qIncrX = FloatToFixed( dDeltaX / ((FLOAT)iDistance )); + pBullet->qIncrY = FloatToFixed( dDeltaY / ((FLOAT)iDistance )); + pBullet->qIncrZ = FloatToFixed( dDeltaZ / ((FLOAT)iDistance )); + + pBullet->ddHorizAngle = ddHorizAngle; + + pBullet->fAimed = FALSE; + + pBullet->qCurrX = FloatToFixed( dStartX ) ; + pBullet->qCurrY = FloatToFixed( dStartY ) ; + pBullet->qCurrZ = FloatToFixed( dStartZ ) ; + + pBullet->iImpact = ubImpact; + + pBullet->iRange = Explosive[Item[usExplosiveItem].ubClassIndex].ubFragRange; + // HEADROCK HAM 5.1: Define original point. + pBullet->sOrigGridNo = ((INT32)dStartX) / CELL_X_SIZE + ((INT32)dStartY) / CELL_Y_SIZE * WORLD_COLS; + pBullet->sTargetGridNo = ((INT32)dEndX) / CELL_X_SIZE + ((INT32)dEndY) / CELL_Y_SIZE * WORLD_COLS; + + pBullet->bStartCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dStartZ ); + pBullet->bEndCubesAboveLevelZ = (INT8) CONVERT_HEIGHTUNITS_TO_INDEX( (INT32)dEndZ ); + + // this distance limit only applies in a "hard" sense to fake bullets for chance-to-get-through, + // but is used for determining structure hits by the regular code + pBullet->iDistanceLimit = iDistance; + + pBullet->fTracer = false; + + if(is_client)send_bullet( pBullet, usExplosiveItem ); + FireBullet( pThrower, pBullet, FALSE ); + + ///////////////////////////////////////////////////////////////////////////////////// + // SANDRO - new mercs' records + /* Make a new record for these if you want, but right now it's disabled for fragments. + if( !fFake && ( pFirer->ubProfile != NO_PROFILE ) && ( pFirer->bTeam == gbPlayerNum ) ) + { + // another shot fired + if ( Item[usHandItem].usItemClass == IC_LAUNCHER || Item[usHandItem].grenadelauncher || Item[usHandItem].rocketlauncher || Item[usHandItem].singleshotrocketlauncher || Item[usHandItem].mortar) + gMercProfiles[ pFirer->ubProfile ].records.usMissilesLaunched++; + else if ( Item[usHandItem].usItemClass == IC_THROWING_KNIFE ) + gMercProfiles[ pFirer->ubProfile ].records.usKnivesThrown++; + else + gMercProfiles[ pFirer->ubProfile ].records.usShotsFired++; + } + ///////////////////////////////////////////////////////////////////////////////////// + */ + + //DebugMsg(TOPIC_JA2,DBG_LEVEL_3,String("FireBulletGivenTargetDone")); + + return( TRUE ); +} + INT8 ChanceToGetThrough( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ ) { if ( Item[pFirer->usAttackingWeapon].usItemClass == IC_GUN || Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE || Item[pFirer->usAttackingWeapon].rocketlauncher ) @@ -5177,6 +5416,12 @@ void MoveBullet( INT32 iBullet ) { fIntended = FALSE; } + // HEADROCK HAM 5: Fragments have 100% Chance to Hit. + if (pBullet->fFragment == TRUE) + { + fIntended = TRUE; + uiChanceOfHit = 100; + } //check for structures at target, and calc chance to hit //hayden, disable any structure t avoid collision if(ENABLE_COLLISION) @@ -5270,7 +5515,8 @@ void MoveBullet( INT32 iBullet ) } else if (pStructure->fFlags & STRUCTURE_PERSON) { - if ( MercPtrs[ pStructure->usStructureID ] != pBullet->pFirer ) + // HEADROCK HAM 5: Fragments can hit the shooter. + if ( MercPtrs[ pStructure->usStructureID ] != pBullet->pFirer || pBullet->fFragment == TRUE ) { // in actually moving the bullet, we consider only count friends as targets if the bullet is unaimed // (buckshot), if they are the intended target, or beyond the range of automatic friendly fire hits @@ -5279,7 +5525,8 @@ void MoveBullet( INT32 iBullet ) // ignore *intervening* target if not visible; PCs are always visible so AI will never skip them on that // basis - if (fIntended) + // HEADROCK HAM 5: Fragments should also hit whatever they encounter. + if (fIntended || pBullet->fFragment == TRUE ) { // could hit this person! gpLocalStructure[iNumLocalStructures] = pStructure; @@ -5324,7 +5571,18 @@ void MoveBullet( INT32 iBullet ) if ( MercPtrs[ pStructure->usStructureID ]->bSide != pBullet->pFirer->bSide || pBullet->iLoop > MIN_DIST_FOR_SCARE_FRIENDS ) { // buckshot has only a 1 in 2 chance of applying a suppression point - if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) + // HEADROCK HAM 5: For NCTH, make pellets as effective as any other bullet. + BOOLEAN fInflictSuppression = FALSE; + if (UsingNewCTHSystem() == true) + { + fInflictSuppression = TRUE; + } + else if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) + { + fInflictSuppression = TRUE; + } + + if (fInflictSuppression) { // bullet goes whizzing by this guy! switch ( gAnimControl[ MercPtrs[pStructure->usStructureID]->usAnimState ].ubEndHeight ) @@ -5420,7 +5678,18 @@ void MoveBullet( INT32 iBullet ) pTarget = MercPtrs[ ubTargetID ]; if ( IS_MERC_BODY_TYPE( pTarget ) && pBullet->pFirer->bSide != pTarget->bSide ) { - if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) + // HEADROCK HAM 5: For NCTH, make pellets as effective as any other bullet. + BOOLEAN fInflictSuppression = FALSE; + if (UsingNewCTHSystem() == true) + { + fInflictSuppression = TRUE; + } + else if ( !(pBullet->usFlags & BULLET_FLAG_BUCKSHOT) || Random( 2 ) ) + { + fInflictSuppression = TRUE; + } + + if (fInflictSuppression) { // bullet goes whizzing by this guy! switch ( gAnimControl[ pTarget->usAnimState ].ubEndHeight ) @@ -5879,7 +6148,8 @@ void MoveBullet( INT32 iBullet ) pBullet->sGridNo = MAPROWCOLTOPOS( pBullet->iCurrTileY , pBullet->iCurrTileX ); uiTileInc++; - if(UsingNewCTHSystem()){ + // HEADROCK HAM 5: Ignore if moving a fragment. + if(UsingNewCTHSystem() && pBullet->fFragment == false ){ // HEADROCK HAM 4: This is kind of a hack. I'm measuring the distance the tile has moved in 2D space, // for purposes of determining whether gravity should take effect. FLOAT dDistanceMoved = PythSpacesAway( pBullet->pFirer->sGridNo, pBullet->sGridNo ) * 10.0f; @@ -6827,11 +7097,11 @@ void AdjustTargetCenterPoint( SOLDIERTYPE *pShooter, INT32 iTargetGridNo, FLOAT { if ((pShooter->bDoBurst-1)%3 == 0) { - ScreenMsg( FONT_MCOLOR_LTRED, MSG_INTERFACE, L"%d. Shot goes %2.1f %s and %2.1f %s", pShooter->bDoBurst, dShotOffsetX, szLeftRight, dShotOffsetY, szUpDown ); + ScreenMsg( FONT_MCOLOR_LTRED, MSG_INTERFACE, L"%d. MO: %2.1f, %2.1f - SO: %2.1f, %2.1f - CF: %2.1f, %2.1f", pShooter->bDoBurst, dMuzzleOffsetX, dMuzzleOffsetY, dShotOffsetX, dShotOffsetY, pShooter->dPrevCounterForceX, pShooter->dPrevCounterForceY ); } else { - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d. Shot goes %2.1f %s and %2.1f %s", pShooter->bDoBurst, dShotOffsetX, szLeftRight, dShotOffsetY, szUpDown ); + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"%d. MO: %2.1f, %2.1f - SO: %2.1f, %2.1f - CF: %2.1f, %2.1f", pShooter->bDoBurst, dMuzzleOffsetX, dMuzzleOffsetY, dShotOffsetX, dShotOffsetY, pShooter->dPrevCounterForceX, pShooter->dPrevCounterForceY ); } } } @@ -7007,8 +7277,15 @@ void CalcTargetMovementOffset( SOLDIERTYPE *pShooter, SOLDIERTYPE *pTarget, OBJE // out current animation state. And I think usUIMovementMode tells us what animation state we // last moved in. So if these two values are different, we should be able to assume that the target // has stopped moving. + // HEADROCK HAM 5: Oh god Chris, that's not how it's supposed to work. Changing stance doesn't miraculously + // erase all the ground you've covered. The target has still moved some distance since its previous turn, + // and we ARE taking this into account now regardless of what the target did at the end of the move. We + // are essentially assuming that turns overlap, so the shot isn't being taken at the exact moment after the + // guy has crouched down, it's taking place simultaneously with his sprint. Commented out. + /* if (pTarget->usAnimState != pTarget->usUIMovementMode) sDistanceMoved = 0; + */ if (sDistanceMoved == 0) { @@ -7276,17 +7553,15 @@ void CalcMuzzleSway( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzl // gigantic. Of course, the smaller the Aperture, the more likely we are to deviate only a small distance (or none). // Start by randomizing the general distance of the muzzle point from the center of the target. - FLOAT RandomMuzzleSway = (FLOAT)((FLOAT)(PreRandom( (UINT32)(iAperture * 100) )) / 100.0); + // HEADROCK HAM 5: Using a better distribution system for random values. + FLOAT RandomMuzzleSway = sqrt((FLOAT)PreRandom( 1000 ) / (FLOAT)1000); + + // Randomize an angle of deviation, in Radians. + FLOAT dRandomAngleRadians = (FLOAT)(PreRandom(6283) / 1000.0f); - // Using this value, we randomly determine how much of the distance is lateral movement (to the left or right - // of the target center), and how much is vertical movement (above or below). - FLOAT dDeltaX = (FLOAT)((FLOAT)(PreRandom( (UINT32)(RandomMuzzleSway * 100)) ) / 100.0); - // Reverse pythagorean formula is used to calculate Y axis deviation (above/below) - FLOAT dDeltaY = sqrt((RandomMuzzleSway*RandomMuzzleSway)-(dDeltaX*dDeltaX)); - - // Randomly decide between up or down, and left or right. - INT8 bLeftRight = PreRandom(2)?(1):(-1); - INT8 bUpDown = PreRandom(2)?(1):(-1); + // Trigonometry! + FLOAT dDeltaX = (sin(dRandomAngleRadians) * RandomMuzzleSway) * iAperture; + FLOAT dDeltaY = (cos(dRandomAngleRadians) * RandomMuzzleSway) * iAperture; // The new Vertical Bias constant turns circular deviation into an ellipse. This can be used to reduce the distance // of up/down deviation. To balance things out, lateral (left/right) deviation is increased by the same proportion. @@ -7308,8 +7583,8 @@ void CalcMuzzleSway( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzl // Now that we have the distance on both axes, we can apply it to the Muzzle Offset value which we were fed // from the calling function. - *dMuzzleOffsetX += (dDeltaX * bLeftRight); - *dMuzzleOffsetY += (dDeltaY * bUpDown) * dVerticalBias; + *dMuzzleOffsetX += dDeltaX; + *dMuzzleOffsetY += dDeltaY * dVerticalBias; } FLOAT CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, OBJECTTYPE *pWeapon, UINT32 uiRange ) @@ -7379,13 +7654,19 @@ FLOAT CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dS // So now, iBulletDev is a maximum deviation for any bullet coming out of this weapon. // Next we randomly determine how far away this bullet will fly. - FLOAT RandomDev = (FLOAT)((FLOAT)(PreRandom( (UINT32)(iBulletDev * 100) )) / 100.0); - // Now let's figure out where this puts the Muzzle Point, in relation to the Target Center. - // We do this by randomly selecting a left/right deviation, then using the remainder as - // up/down deviation. - FLOAT dDeltaX = (FLOAT)((FLOAT)(PreRandom( (UINT32)(RandomDev * 100)) ) / 100.0); - FLOAT dDeltaY = sqrt((RandomDev*RandomDev)-(dDeltaX*dDeltaX)); + // HEADROCK HAM 5: Using a better formula to distribute shots evenly within the deviation + // aperture. + + // Randomize a deviation as a value between 0.0 and 1.0, then sqrt to give an even dispersal. + FLOAT dRandomDeviation = (FLOAT)((FLOAT)PreRandom(1000) / 1000.f); + + // Randomize an angle of deviation, in Radians. + FLOAT dRandomAngleRadians = (FLOAT)(PreRandom(6283) / 1000.0f); + + // Trigonometry! + FLOAT dDeltaX = (sin(dRandomAngleRadians) * dRandomDeviation * iBulletDev); + FLOAT dDeltaY = (cos(dRandomAngleRadians) * dRandomDeviation * iBulletDev); // Over increasing range, a bullet fired with the same deviation should actually end up // further away from the target than if the target was closer. We use the constant @@ -7396,13 +7677,9 @@ FLOAT CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dS dDeltaX *= iDistanceRatio; dDeltaY *= iDistanceRatio; - // Randomly determine whether the bullet goes up or down, and left or right. - INT8 bLeftRight = PreRandom(2)?(1):(-1); - INT8 bUpDown = PreRandom(2)?(1):(-1); - // Finally, adjust the center point of our shot to correspond with the deviation. - *dShotOffsetX += dDeltaX * bLeftRight; - *dShotOffsetY += dDeltaY * bUpDown; + *dShotOffsetX += dDeltaX; + *dShotOffsetY += dDeltaY; return (iBulletDev); } @@ -7745,6 +8022,28 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu // of the gun and keep it pointed at least close to the torso. If their volley had started // while pointed at the torso, they'd be firing above the target by now... + FLOAT dOffsetX = 0.0f; + FLOAT dOffsetY = 0.0f; + FLOAT dCounterForceX = 0.0f; + FLOAT dCounterForceY = 0.0f; + + UINT32 uiIntendedBullets; + if ( pShooter->bDoAutofire > 0 ) + { + // Autofire. The number of bullets to be fired equals to the value of bDoAutofire + uiIntendedBullets = pShooter->bDoAutofire; + } + else + { + // Burst. Get the number of shots-per-burst from the weapon. + uiIntendedBullets = Weapon[ pWeapon->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pWeapon); + } + if (uiIntendedBullets == 1) + { + // Only one bullet being fired. Forget the rest. + return; + } + // Calculating Pre-Recoil Offset is fairly simple. We begin by determining the chance of // this soldier to actually consider using pre-recoil offset at all. This depends entirely // on wisdom and experience. @@ -7753,7 +8052,15 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu INT8 bGunRecoilX; INT8 bGunRecoilY; - FLOAT dDistanceRatio = (FLOAT)(uiRange / gGameCTHConstants.NORMAL_RECOIL_DISTANCE); + + // Get the gun's recoil values for our first bullet. + GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, 1 ); + + // Calculate the Distance Ratio for later use. + FLOAT dDistanceRatio = (FLOAT)uiRange / (FLOAT)gGameCTHConstants.NORMAL_RECOIL_DISTANCE; + + // Calculate the various counter-force related values for our shooter. + FLOAT dCounterForceMax = CalcCounterForceMax(pShooter, pWeapon); UINT8 stance = gAnimControl[ pShooter->usAnimState ].ubEndHeight; @@ -7766,32 +8073,10 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu FLOAT iCounterForceMax = ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100); UINT32 uiCounterForceAccuracy = CalcCounterForceAccuracy(pShooter, pWeapon, uiRange, FALSE, true); - UINT32 uiCounterForceFrequency = CalcCounterForceFrequency(pShooter, pWeapon); - - iCounterForceMax = iCounterForceMax * (100-uiCounterForceAccuracy) / 100; - GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, 1 ); - FLOAT iGunTotalRecoil = (FLOAT)bGunRecoilX + (FLOAT)bGunRecoilY; - FLOAT iIdealCounterForceX = 0.0; - FLOAT iIdealCounterForceY = 0.0; - - if(iGunTotalRecoil != 0) - { - iIdealCounterForceX = __min( iCounterForceMax, (FLOAT)bGunRecoilX ); - iIdealCounterForceY = __min( iCounterForceMax, (FLOAT)bGunRecoilY ); - FLOAT iTotalCounterForce = sqrt((abs(iIdealCounterForceX)*abs(iIdealCounterForceX)) + (abs(iIdealCounterForceY)*abs(iIdealCounterForceY))); - if (iTotalCounterForce > iCounterForceMax) - { - FLOAT iRatio = iTotalCounterForce / iCounterForceMax; - if (iRatio != 0) - { - iIdealCounterForceX /= iRatio; - iIdealCounterForceY /= iRatio; - } - } - pShooter->dPrevCounterForceX = -iIdealCounterForceX; - pShooter->dPrevCounterForceY = -iIdealCounterForceY; - } + //////////////////////////////////////////////////////////// + // Calculate the soldier's expertise to determine whether he's smart enough to compensate + // for recoil before it begins at all! INT8 traitLoop; FLOAT iCombinedSkill = gGameCTHConstants.PRE_RECOIL_WIS * EffectiveWisdom(pShooter); iCombinedSkill += gGameCTHConstants.PRE_RECOIL_EXP_LEVEL * EffectiveExpLevel(pShooter) * 10; @@ -7817,77 +8102,123 @@ void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMu // Limit to a 0-100 scale, just in case. UINT32 uiPreRecoilAbility = (UINT32)__min(100, __max(iCombinedSkill, 0)); - // Randomly determine whether the soldier has compensated for recoil this time. - if (PreRandom(100) >= uiPreRecoilAbility) + // End skill calculation. + ///////////////////////////////////////////////////////// + + // HEADROCK HAM 5: We'll begin by checking whether the soldier was smart/experienced enough + // to aim AWAY from the target's center before firing his volley. By aiming low and to the left, for + // example, the soldier should have an easier time leading his volley into the target with + // subsequent bullets (assuming the gun is pulling up and right, of course). + // We can adjust by up to one recoil-length away - not much, but helpful. + + // Randomly determine whether the soldier has compensated by moving his muzzle. + /* + if (PreRandom(100) < uiPreRecoilAbility) { - // Soldier has forfeited his chance. Make no adjustments. - return; - } - - // Now that we've determined that this shooter will in fact compensate for recoil, we want to know - // how much to compensate by. We start by figuring out how many bullets will leave the gun before the - // shooter gets his chance to begin applying counter-force. He'll need to pre-compensate for this - // many bullets. - - // Also, it might be possible that we don't intend to fire that many bullets at all. So we want to figure out - // how many bullets are about to be fired, and adjust accordingly. - UINT32 uiIntendedBullets; - if ( pShooter->bDoAutofire > 0 ) - { - // Autofire. The number of bullets to be fired equals to the value of bDoAutofire - uiIntendedBullets = pShooter->bDoAutofire; - } - else - { - // Burst. Get the number of shots-per-burst from the weapon. - uiIntendedBullets = Weapon[ pWeapon->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pWeapon); - } - - // Which one's smaller? - uiIntendedBullets = __min( uiIntendedBullets, uiCounterForceFrequency ); - - // Now, let's figure out how much recoil this gun will put out during those first X bullets. - FLOAT iTotalGunRecoilX = 0; - FLOAT iTotalGunRecoilY = 0; - - // For each bullet, add up its recoil. - for (UINT32 count=1; count <= uiIntendedBullets; count++) - { - GetRecoil( pShooter, pWeapon, &bGunRecoilX, &bGunRecoilY, count ); - iTotalGunRecoilX += (bGunRecoilX + pShooter->dPrevCounterForceX); - iTotalGunRecoilY += (bGunRecoilY + pShooter->dPrevCounterForceY); - } - - if (iTotalGunRecoilX == 0 && iTotalGunRecoilY == 0) - { - // The weapon produces no recoil within these few shots. Either it has no recoil at all, or is designed - // to produce no recoil during the first shots. There's no need to compensate for pre-recoil. - return; - } - - // Next up, let's find out whether our shooter will correctly compensate for both X and Y recoil. - // Invert RecoilAbility value. 0=best, 100=worst. - uiPreRecoilAbility = (100-uiPreRecoilAbility); - - // Randomly decide how much compensation is performed - FLOAT dPreRecoilX = (FLOAT)(PreRandom(uiPreRecoilAbility)/100.0f) * iTotalGunRecoilX; - FLOAT dPreRecoilY = (FLOAT)(PreRandom(uiPreRecoilAbility)/100.0f) * iTotalGunRecoilY; + // The soldier is smart enough to compensate for recoil by not aiming for the center of the target. + // He will attempt to adjust his aim by one recoil's length, causing the second bullet to impact + // with the target's center. This will increase controllability of the remainder of the volley. - // Decide over/under compensation randomly - INT8 bUpDown = PreRandom(2)?(1):(-1); - INT8 bLeftRight = PreRandom(2)?(1):(-1); + FLOAT dIdealOffsetX = (FLOAT)-bGunRecoilX; + FLOAT dIdealOffsetY = (FLOAT)-bGunRecoilY; - // Apply random factor - dPreRecoilX = iTotalGunRecoilX + (dPreRecoilX * bLeftRight); - dPreRecoilY = iTotalGunRecoilY + (dPreRecoilY * bUpDown); + // Naturally, getting it right is remarkably tricky. We might undercompensate or overcompensate! + // Our pre-recoil ability will help us do this more accurately. + FLOAT dMaxError = 0.0f; + FLOAT dError = 0.0f; + INT8 bErrorDirection = 0; - // Apply distance ratio. The further away we are, the more compensation we need. - dPreRecoilX *= dDistanceRatio; - dPreRecoilY *= dDistanceRatio; + // FOR X: + dMaxError = (dIdealOffsetX * (100-uiPreRecoilAbility)) / 100; + dError = ((FLOAT)Random((INT32)(abs(dMaxError) * 1000))) / 1000.0f; + bErrorDirection = Random(2) ? 1 : (-1); + // Set final muzzle compensation + dOffsetX = dIdealOffsetX + (dError * bErrorDirection); + // FOR Y: + dMaxError = (dIdealOffsetY * (100-uiPreRecoilAbility)) / 100; + dError = ((FLOAT)Random((INT32)(abs(dMaxError) * 1000))) / 1000.0f; + bErrorDirection = Random(2) ? 1 : (-1); + // Set final muzzle compensation + dOffsetY = dIdealOffsetY + (dError * bErrorDirection); + } + */ - // Finally, adjust our muzzle direction. - *dMuzzleOffsetX -= dPreRecoilX; - *dMuzzleOffsetY -= dPreRecoilY; + // Randomly determine whether the soldier has compensated by applying counter-force. + if (PreRandom(100) < uiPreRecoilAbility) + { + // HEADROCK HAM 5: Since we can now alter CF with every shot, but only by a small amount, we can allow + // our shooter to "anticipate" recoil by applying at least part of the required CF to stabilize the + // muzzle. + + // First lets figure out what the "ideal" CF would be. Ideal CF application would result in the muzzle + // moving towards the 0,0 coordinate (target center) with some velocity, but not more velocity than it + // would take to stabilize the muzzle for our next shot. + + // The basic ideal is CF = -Recoil. If we can achieve this, then our muzzle will be completely stationary + // after the first bullet is fired, allowing us to adjust CF as we please during the following CF check. + FLOAT dIdealCounterForceX = (FLOAT)-bGunRecoilX; + FLOAT dIdealCounterForceY = (FLOAT)-bGunRecoilY; + + // But we also want the volley to be moving at least somewhat toward the target. Therefore, lets figure + // out where our muzzle is, and adjust in that direction by as much force as could be undone by the next + // bullet. + + // Maximum velocity change. This is how fast the muzzle can move while still being easy to stop moving + // within one bullet cycle. + FLOAT dMaxChange = gGameCTHConstants.RECOIL_COUNTER_INCREMENT; + + // For X: + INT8 bMuzzleDirectionX = (INT8)(*dMuzzleOffsetX / abs(*dMuzzleOffsetX)); + dIdealCounterForceX += (-1) * bMuzzleDirectionX * dMaxChange; + // For Y: + INT8 bMuzzleDirectionY = (INT8)(*dMuzzleOffsetY / abs(*dMuzzleOffsetY)); + dIdealCounterForceY += (-1) * bMuzzleDirectionY * dMaxChange; + + // Lets figure out how close we come to actually applying this exact amount of CF? + FLOAT dMaxError = 0.0f; + FLOAT dError = 0.0f; + INT8 bErrorDirection = 0; + + // FOR X: + dMaxError = (dIdealCounterForceX * (100-uiPreRecoilAbility)) / 100; + dError = ((FLOAT)Random((INT32)(abs(dMaxError) * 1000))) / 1000.0f; + bErrorDirection = Random(2) ? 1 : (-1); + // Set final counter-force + dCounterForceX = dIdealCounterForceX + (dError * bErrorDirection); + // FOR Y: + dMaxError = (dIdealCounterForceY * (100-uiPreRecoilAbility)) / 100; + dError = ((FLOAT)Random((INT32)(abs(dMaxError) * 1000))) / 1000.0f; + bErrorDirection = Random(2) ? 1 : (-1); + // Set final counter-force + dCounterForceY = dIdealCounterForceY + (dError * bErrorDirection); + + // We obviously can't apply more than our shooter's Max Counter-Force, so lets use Pythagorean Theorem + // to "nerf" counterforce on both axes to the given limit. + FLOAT dTotalCounterForce = sqrt((abs(dCounterForceX)*abs(dCounterForceX)) + (abs(dCounterForceY)*abs(dCounterForceY))); + if (dTotalCounterForce > dCounterForceMax) + { + FLOAT dRatio = dTotalCounterForce / dCounterForceMax; + if (dRatio != 0) + { + dCounterForceX /= dRatio; + dCounterForceY /= dRatio; + } + } + } + + // So far we've been calculating everything at NORMAL distance, so lets apply distance to the target + // to convert this into actual muzzle point deviation. + dOffsetX *= dDistanceRatio; + dOffsetY *= dDistanceRatio; + + // Adjust our muzzle point! + *dMuzzleOffsetX += dOffsetX; + *dMuzzleOffsetY += dOffsetY; + + // Also, lets set our shooter's Counter Force variables. These will apply for the next bullet in the + // volley. + pShooter->dPrevCounterForceX = dCounterForceX; + pShooter->dPrevCounterForceY = dCounterForceY; } @@ -7919,250 +8250,556 @@ void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuz if(bGunRecoilX == 0 && bGunRecoilY == 0) return; - FLOAT iGunTotalRecoil = sqrt((FLOAT)(bGunRecoilX*bGunRecoilX) + (FLOAT)(bGunRecoilY*bGunRecoilY)); - - FLOAT iDistanceRatio = (FLOAT)uiRange / (FLOAT)gGameCTHConstants.NORMAL_RECOIL_DISTANCE; + FLOAT dDistanceRatio = (FLOAT)uiRange / (FLOAT)gGameCTHConstants.NORMAL_RECOIL_DISTANCE; // These variables will hold the amount of X/Y force our shooter exerts to try to fight recoil. - FLOAT iAppliedCounterForceX; - FLOAT iAppliedCounterForceY; - - - ////////////////////////////////////////////////////////////////////////////////////// - // Counter-Force Frequency - // - // Counter force applied is recorded into the character's data. The same amount of force is applied to - // a few consecutive bullets, and then the shooter gets the chance to alter their counter force. - // Skilled shooters get this chance more often. - // Tracer fire also helps. Once a tracer bullet is fired, we get one free chance to alter our counter-force. - - UINT32 uiCounterForceFrequency = CalcCounterForceFrequency(pShooter, pWeapon); - - BOOLEAN fAdjustmentAllowed = FALSE; - if ((pShooter->bDoBurst-1)%uiCounterForceFrequency == 0) - { - fAdjustmentAllowed = TRUE; - } + FLOAT dAppliedCounterForceX = pShooter->dPrevCounterForceX; + FLOAT dAppliedCounterForceY = pShooter->dPrevCounterForceY; BOOLEAN fTracer = WasPrevBulletATracer( pShooter, pWeapon ); - if (!fAdjustmentAllowed && !fTracer) + /////////////////////////////////////////////////////////////////////////////////// + // STEP 2: + // + // Our shooter got the chance to change the amount of force he's using to counter recoil. + + // The "Next Offset" here signifies how far off the center of the target our next bullet will be, in X/Y terms. + // The optimal of course would be if both are equal to 0, meaning the shot will be directed towards the center. + // However, it's up to the shooter to make sure that happens, by applying counter-force. + // Our shooter wants to apply just as much counter-force as necessary to keep the next offset as close to 0 on both + // axes. + + // Instead of assuming we're on a uiCounterForceFrequency round, we need to verify exactly where we are in the uiCounterForceFrequency + // sequence. + INT32 uiIntendedBullets; + if ( pShooter->bDoAutofire > 0 ) { - // Not time for a force adjustment. Shooter will use the same amount of force he previously used. - iAppliedCounterForceX = pShooter->dPrevCounterForceX; - iAppliedCounterForceY = pShooter->dPrevCounterForceY; + // Autofire. The number of bullets to be fired equals to the value of bDoAutofire + uiIntendedBullets = pShooter->bDoAutofire; } else { - /////////////////////////////////////////////////////////////////////////////////// - // STEP 2: - // - // Our shooter got the chance to change the amount of force he's using to counter recoil. + // Burst. Get the number of shots-per-burst from the weapon. + uiIntendedBullets = Weapon[ pWeapon->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pWeapon); + } - // The "Next Offset" here signifies how far off the center of the target our next bullet will be, in X/Y terms. - // The optimal of course would be if both are equal to 0, meaning the shot will be directed towards the center. - // However, it's up to the shooter to make sure that happens, by applying counter-force. - // Our shooter wants to apply just as much counter-force as necessary to keep the next offset as close to 0 on both - // axes. + //////////////////////////////////////////////////////////////////////////// + // Calculating recoil counter-force. + // + // The shooter needs to apply a specific amount of force in order to counter-act recoil, not just for this + // bullet, but for all others that came before. + // + // To do this, the shooter needs skills. One set of skills allows the shooter to exert more counter-force. A + // shooter with insufficient strength will be unable to counter-act the recoil from a powerful gun. A shooter + // with high strength can potentially hold the gun steady even while its firing. + // + // However, the shooter also needs a second set of skills to help him predict exactly how much force is required + // between every bullet, and the ability to actually exert the exact amount of force required. Exerting too little + // force leads to under-compensation: The shooter will need to exert even MORE force for the next bullet! Exerting + // too MUCH force is also a potential problem, as the gun may miss the target on the OTHER side. Of course, + // it's generally better to exert too much force than too little, assuming you don't repeatedly over-compensate, + // in which case you'll be shooting at the ground. - // Instead of assuming we're on a uiCounterForceFrequency round, we need to verify exactly where we are in the uiCounterForceFrequency - // sequence. - INT32 uiIntendedBullets; - if ( pShooter->bDoAutofire > 0 ) + // STEP 1: determining how much counter-force the shooter can apply if necessary. This is the absolute + // maximum counter-force that can be applied. By default, it is based primarily on the strength of the shooter, + // although agility is also helpful. + + FLOAT dCounterForceMax = CalcCounterForceMax(pShooter, pWeapon); + + UINT8 stance = gAnimControl[ pShooter->usAnimState ].ubEndHeight; + + // Flugente: new feature: if the next tile in our sight direction has a height so that we could rest our weapon on it, we do that, thereby gaining the prone boni instead. This includes bipods + if ( gGameExternalOptions.fWeaponResting && pShooter->IsWeaponMounted() ) + stance = ANIM_PRONE; + + FLOAT moda = CalcCounterForceMax(pShooter, pWeapon, stance); + FLOAT modb = CalcCounterForceMax(pShooter, pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight); + FLOAT iCounterForceMax = ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100); + + // iCounterForceMax is now the absolute limit. + + // STEP 2: Now we need to determine how accurate the shooter is when applying counter-force. He won't always apply + // as much as necessary, and may sometimes apply too much. The ability to apply exactly (or close to exactly) the + // amount of force required is based on various skills, especially agility and dexterity. Wisdom and experience also + // help, and the AUTO_WEAPONS skill is invaluable. In addition, tracers (when actually fired) will boost the + // ability to compensate correctly by a certain amount. + + UINT32 uiCounterForceAccuracy = CalcCounterForceAccuracy(pShooter, pWeapon, uiRange, fTracer); + + // STEP 3: Now let's determine how much counter-force would be ideal. If the gun isn't kicking too powerfully, + // and/or not too much recoil has built up from previous bullets, the shooter should potentially be able to compensate + // for ALL of it. However, in some cases, the shooter simply can't exert enough force to counter ALL recoil. + + // HEADROCK HAM 5: Unfortunately, In an attempt to correct a conceptual problem with calculating ideal CF, + // ChrisL created a formula that was impossible to decypher. Supposedly, the formula would cause autofire to + // move towards a different point than the center, then slowly correct that point. I got three different coders + // to look at it and they couldn't see how it works. + // Unfortunately, short of asking Chris to redo it, I couldn't see any other way to rewrite the + // formula so that it is legible. Instead of bothering Chris about it I decided to just rewrite the entire system + // for additional realism, based on a superior idea I had before work on NCTH began. + + // Instead of being forced to use the same CF for several consecutive shots, the shooter's CF will actually + // increase or decrease as required with every bullet. In other words, with each bullet fired, CF can + // be changed. However, we enforce a given cap which is significantly lower than Max CF or any common recoil values, + // meaning that the shooter needs to make many adjustments in order to keep the volley going in the right, + // with inherent errors causing it to be much more difficult overall to keep the gun trained directly at the + // target regardless of CF accuracy. + + // For more explanation read the comments inside the following function: + + // CALCULATE FOR X + FLOAT dCounterForceChangeX = CalcCounterForceChange( pShooter, uiCounterForceAccuracy, dCounterForceMax, *dMuzzleOffsetX / dDistanceRatio, bGunRecoilX, pShooter->dPrevCounterForceX, uiIntendedBullets ); + // CALCULATE FOR Y + FLOAT dCounterForceChangeY = CalcCounterForceChange( pShooter, uiCounterForceAccuracy, dCounterForceMax, *dMuzzleOffsetY / dDistanceRatio, bGunRecoilY, pShooter->dPrevCounterForceY, uiIntendedBullets ); + + dAppliedCounterForceX += dCounterForceChangeX; + dAppliedCounterForceY += dCounterForceChangeY; + + ///////////////////////////// + // Use pythagorean to scale this to the max force allowed. + // We want to use pythagorean here so that we calculate the total "vector length" of recoil and compare that to CFM + FLOAT dTotalAppliedCounterForce = sqrt((dAppliedCounterForceX*dAppliedCounterForceX) + (dAppliedCounterForceY*dAppliedCounterForceY)); + if (dTotalAppliedCounterForce > dCounterForceMax) + { + FLOAT dRatio = dTotalAppliedCounterForce / dCounterForceMax; + if (dRatio != 0) { - // Autofire. The number of bullets to be fired equals to the value of bDoAutofire - uiIntendedBullets = pShooter->bDoAutofire; + dAppliedCounterForceX /= dRatio; + dAppliedCounterForceY /= dRatio; + } + } + // This works because sqrt((iACFX^2)+(iACFY^2)) should result in being less then or equal to CFM + + // Record how much counter force was applied this time. It will be used for the next few shots until the + // shooter can recalculate. + pShooter->dPrevCounterForceX = dAppliedCounterForceX; + pShooter->dPrevCounterForceY = dAppliedCounterForceY; + + dAppliedCounterForceX += (FLOAT)bGunRecoilX; + dAppliedCounterForceY += (FLOAT)bGunRecoilY; + + // So far we've been calculating everything at NORMAL distance, so we need to apply a distance ratio to + // multiply all muzzle movements as distance increases or decreases. + dAppliedCounterForceX *= dDistanceRatio; + dAppliedCounterForceY *= dDistanceRatio; + + // Offset the bullet. + *dMuzzleOffsetX += dAppliedCounterForceX; + *dMuzzleOffsetY += dAppliedCounterForceY; + + +} + +FLOAT CalcCounterForceChange( SOLDIERTYPE * pShooter, UINT32 uiCounterForceAccuracy, FLOAT dCounterForceMax, FLOAT dMuzzleOffset, INT8 bRecoil, FLOAT dPrevCounterForce, UINT32 uiIntendedBullets ) +{ + ///////////////////////////////////////////////////////////////////////////////////////// + // HEADROCK HAM 5: New Counter-Force Calculation + // + // This function, called from the main NCTH ballistic formula, handles the adjustment of + // Counter Force between each and every bullet during an Autofire or Burst volley. + // + // Counter-Force (CF) is the power used by the shooter to control his gun as it jerks back + // (Recoil, or Muzzle Climb). CF can be exerted to reduce recoil, it can be loosened up to let recoil + // move the gun on its own, or it can be used to push the muzzle of the gun in any necessary direction. + // The goal is to bring the muzzle towards that "perfect" trajectory needed to fire subsequent + // bullets into the exact center of the target (coordinate 0,0). + // + // After each bullet is fired, the muzzle moves a certain amount either towards or away + // from the target's center - depending on the difference between Recoil and Counter Force + // at the time. Positive movement on the X axis means the muzzle is moving towards the right, + // negative means it is moving towards the left. For the Y axis this is up and down respectively. + // Again, the shooter's goal is to move the muzzle from whereever it currently is pointing towards + // coordinates 0,0. + // Recoil (usually) remains consistent throughout the volley, so to move the muzzle correctly to 0,0 + // our shooter must constantly adjust the amount of counter-force he is applying - affecting the + // difference between Recoil and CF and thus generating movement in the desired direction. + // + // After the first bullet is fired, the shooter is already applying some counter-force, generally + // in the direction of the target center - helping to overcome some initial recoil. From that point + // onwards, counter-force must be adjusted after every bullet, changing the muzzle's angular velocity + // to get the muzzle going in the right direction or slowing it down when it's getting close to the target. + // + // The trick is that these changes in velocity must be made in relatively small amounts - no sudden + // changes from pushing the barrel one way to pushing it the other way, but rather gradual + // changes that could take several "bullets" to complete. In other words, several bullets might be + // fired while the shooter slowly changes the muzzle velocity towards the desired direction. Visually, + // this would seem like the volley's movements decelerate, then start moving in another direction + // (hopefully back towards the target). + // + // Getting the muzzle to coordinates 0,0 or thereabouts is not sufficient on its own. If the muzzle + // is moving fast towards 0,0, it may sweep over that coordinate. Optimally, the shooter wants to + // reach 0,0 while at the same time having the right amount of CF to hold the muzzle there against + // the gun's recoil - resulting in the muzzle being both pointed directly at the target AND completely + // stationary. Doing so is very hard, especially due to the program enforcing "mandatory errors" in + // CF application - mimicing the virtual impossibility for the human body to fully stabilize an + // autofiring gun. Radical errors could send the muzzle flying in some direction, wasting several + // more bullets on empty space while the shooter struggles to regain control. + // + // The formula below handles the majority of this process. It calculates how much CHANGE must be + // made to our current Counter-Force before the next bullet is fired. It factors in the acceleration and + // deceleration required to finesse the muzzle back to coordinate 0,0. It calculates how many + // rounds it would take to get there. It also enforces both a maximum amount of change per + // bullet as well as the mandatory ERROR in shooter CF application. + // Finally, the function returns the amount of CF Change to be applied this cycle. + // + // NOTE: This function has to be run TWICE: once for the X axis, the other for the Y axis. Feed + // different arguments in to get one or the other. + + ///////////////// MAXIMUM INCREMENT + + // This INI value defines the maximum allowed change of velocity between any two bullets in the + // volley. This is used to limit any attempted change, and also helps measure the potential movements + // of the muzzle. + // Note: The limit applies BEFORE mandatory aiming errors are taken into account. + FLOAT dMaxIncrement = gGameCTHConstants.RECOIL_COUNTER_INCREMENT; + + ///////////////// IDEAL COUNTER-FORCE + + // To begin, we're going to calculate an "IDEAL" CF. In other words, we're checking to see + // how much CF is currently required to bring the muzzle exactly to coordinate 0,0 in one + // movement - ignoring current CF or any limit on changing CF. + + // First calculate how far our muzzle currently is from the target's center. Positive is + // right (or up), negative is left (or down). + FLOAT dIdealCF = dMuzzleOffset; + + // Add the gun's Recoil to this, as that's how far the muzzle will end up if we let + // recoil go unopposed. + dIdealCF += bRecoil; + + // The remainder is how much help we need from CF to get our muzzle exactly on the target. + // We need to invert this now, because CF must work TOWARDS 0,0 , i.e. decreasing a right/up-offset + // and increasing a left/down-offset. + dIdealCF *= -1; + + // Lets also record the direction of the target. + INT8 bDirectionTarget = (-1) * (INT8)(dMuzzleOffset / abs(dMuzzleOffset)); + + // Lets track a separate value for the actual direction of the target + INT8 bDirectionIdeal = (INT8)(dIdealCF / abs(dIdealCF)); + + // We now know how much CF we need to get back to the target's center. Let's subtract + // the amount of CF we're already applying from the previous bullet. This tells us by how much we + // need to change the current CF in order to get the muzzle all the way back to 0,0 with our + // current bullet. + FLOAT dIdealDelta = dIdealCF - dPrevCounterForce; + + ///////////////// SITUATIONS + + // So now we know how much CF we would need, and how much change we need to make to get to that CF. + // However, since we are enforcing a maximum amount of change with every bullet, any change + // has to be carefully considered. If we change CF too radically towards the target, we may end + // up sweeping across it. In other words, when we get to 0,0, our CF may cause us to keep + // moving the muzzle in that direction afterwards, causing us to end up on the other side of + // 0,0 with the muzzle speeding off further away! Ideally, we want to get to 0,0 and simultaneously + // also reach CF = -Recoil (in other words, balancing them out so that the muzzle doesn't move + // AWAY from 0,0). This is tricky, again, because we can only change CF by a small amount each + // time we run this function. + // The section below handles this, by actively measuring what would happen if we altered CF, whether + // reducing it or increasing it, and trying to figure out the best way to reach 0,0 coordinates + // with exactly CF = -Recoil. + + // Start with something easy: is the muzzle currently motionless? ( CF = -Recoil ) + if (dPrevCounterForce == -bRecoil) + { + ///////////////// STATIONARY MUZZLE + + // We are currently applying exactly as much CF as we need to keep the muzzle stationary. + // Ok, are we at the target center? + if (dMuzzleOffset == 0) + { + //Perfect! This is where we want to be! Keep the muzzle steady by simply not changing anything. + dIdealDelta = 0; + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Muzzle stable :: No change required."); + } } else { - // Burst. Get the number of shots-per-burst from the weapon. - uiIntendedBullets = Weapon[ pWeapon->usItem ].ubShotsPerBurst + GetBurstSizeBonus(pWeapon); - } - INT8 iRound = (pShooter->bDoBurst-1) % uiCounterForceFrequency; - if(iRound == 0) - iRound = uiCounterForceFrequency; - if((pShooter->bDoBurst - 1 + iRound) > uiIntendedBullets) - iRound = uiIntendedBullets - (pShooter->bDoBurst - 1); + // We need to accelerate in the direction of the target center. Because we are currently motionless, + // then no matter how much acceleration we add right now we'll be able to undo it during the next + // step - i.e. one step accelerating, next step we could easily decelerate back to a stationary + // state, or keep accelerating if necessary. In either case, it's safe to accelerate towards + // 0,0. - // Add the two together to figure out where the next bullet is going to fly in relation to the target. - FLOAT dNextOffsetX = (FLOAT)(*dMuzzleOffsetX + (bGunRecoilX * iDistanceRatio) ); - FLOAT dNextOffsetY = (FLOAT)(*dMuzzleOffsetY + (bGunRecoilY * iDistanceRatio) ); - //FLOAT dNextOffsetX = (FLOAT)(*dMuzzleOffsetX + ((bGunRecoilX * iDistanceRatio) * uiCounterForceFrequency) ); - //FLOAT dNextOffsetY = (FLOAT)(*dMuzzleOffsetY + ((bGunRecoilY * iDistanceRatio) * uiCounterForceFrequency) ); - - //////////////////////////////////////////////////////////////////////////// - // Calculating recoil counter-force. - // - // The shooter needs to apply a specific amount of force in order to counter-act recoil, not just for this - // bullet, but for all others that came before. - // - // To do this, the shooter needs skills. One set of skills allows the shooter to exert more counter-force. A - // shooter with insufficient strength will be unable to counter-act the recoil from a powerful gun. A shooter - // with high strength can potentially hold the gun steady even while its firing. - // - // However, the shooter also needs a second set of skills to help him predict exactly how much force is required - // between every bullet, and the ability to actually exert the exact amount of force required. Exerting too little - // force leads to under-compensation: The shooter will need to exert even MORE force for the next bullet! Exerting - // too MUCH force is also a potential problem, as the gun may miss the target on the OTHER side. Of course, - // it's generally better to exert too much force than too little, assuming you don't repeatedly over-compensate, - // in which case you'll be shooting at the ground. - - // STEP 1: determining how much counter-force the shooter can apply if necessary. This is the absolute - // maximum counter-force that can be applied. By default, it is based primarily on the strength of the shooter, - // although agility is also helpful. - - UINT8 stance = gAnimControl[ pShooter->usAnimState ].ubEndHeight; - - // Flugente: new feature: if the next tile in our sight direction has a height so that we could rest our weapon on it, we do that, thereby gaining the prone boni instead. This includes bipods - if ( gGameExternalOptions.fWeaponResting && pShooter->IsWeaponMounted() ) - stance = ANIM_PRONE; - - FLOAT moda = CalcCounterForceMax(pShooter, pWeapon, stance); - FLOAT modb = CalcCounterForceMax(pShooter, pWeapon, gAnimControl[ pShooter->usAnimState ].ubEndHeight); - FLOAT iCounterForceMax = ((gGameExternalOptions.ubProneModifierPercentage * moda + (100 - gGameExternalOptions.ubProneModifierPercentage) * modb)/100); - - // iCounterForceMax is now the absolute limit. - - // STEP 2: Now we need to determine how accurate the shooter is when applying counter-force. He won't always apply - // as much as necessary, and may sometimes apply too much. The ability to apply exactly (or close to exactly) the - // amount of force required is based on various skills, especially agility and dexterity. Wisdom and experience also - // help, and the AUTO_WEAPONS skill is invaluable. In addition, tracers (when actually fired) will boost the - // ability to compensate correctly by a certain amount. - - UINT32 uiCounterForceAccuracy = CalcCounterForceAccuracy(pShooter, pWeapon, uiRange, fTracer); - - // STEP 3: Now let's determine how much counter-force would be ideal. If the gun isn't kicking too powerfully, - // and/or not too much recoil has built up from previous bullets, the shooter should potentially be able to compensate - // for ALL of it. However, in some cases, the shooter simply can't exert enough force to counter ALL recoil. - // ChrisL: Here we need a little change. As the above comment states, we do want to try and compensate for ALL recoil which makes sense. - // And it also makes sense that we try and compensate for any recoil we previously under-compensated for. But what we're actually doing - // is trying to compensate for all recoil AND bring our offsets back to 0x0 which makes autofire incredibly accurate regardless of what - // our shots initial accuracy was. So instead of trying to compensate down to 0x0, we'll instead try to compensate back to whatever our - // initial offsets were. However, since autofire should be a bit more accurate then single shot (since we're able to "walk" rounds to the - // target) we need to adjust the initial offsets a little, the more rounds we fire. - - INT8 iImprovement = __min(100,(INT8)((pShooter->bDoBurst-1) / uiCounterForceFrequency) * (fTracer?gGameCTHConstants.RECOIL_COUNTER_INCREMENT_TRACER:gGameCTHConstants.RECOIL_COUNTER_INCREMENT)); - - // Calculate Ideal X and limit to max force - //FLOAT iIdealCounterForceX = -(bGunRecoilX + (*dMuzzleOffsetX / iDistanceRatio / iRound) ); - FLOAT iInitialOffsetX = pShooter->dInitialMuzzleOffsetX; - FLOAT iBestImprovementX = iInitialOffsetX * (((FLOAT)(100-uiCounterForceAccuracy)/gGameCTHConstants.RECOIL_COUNTER_ACCURACY_COMPENSATION)/100.0f); - FLOAT iTargetOffsetX = iInitialOffsetX - (iBestImprovementX * iImprovement / 100); - FLOAT iIdealCounterForceX = -(bGunRecoilX + ((*dMuzzleOffsetX - iTargetOffsetX) / iDistanceRatio / iRound)); - - // Calculate Ideal Y and limit to max force - //FLOAT iIdealCounterForceY = -(bGunRecoilY + (*dMuzzleOffsetY / iDistanceRatio / iRound) ); - FLOAT iInitialOffsetY = pShooter->dInitialMuzzleOffsetY; - FLOAT iBestImprovementY = iInitialOffsetY * (((FLOAT)(100-uiCounterForceAccuracy)/gGameCTHConstants.RECOIL_COUNTER_ACCURACY_COMPENSATION)/100.0f); - FLOAT iTargetOffsetY = iInitialOffsetY - (iBestImprovementY * iImprovement / 100); - FLOAT iIdealCounterForceY = -(bGunRecoilY + ((*dMuzzleOffsetY - iTargetOffsetY) / iDistanceRatio / iRound)); - - // STEP 4: Find out whether the shooter is over-compensating or under-compensating, and by how much. Higher - // force-accuracy will decrease this deviation, generating a resulting counter-force that's closer to the ideal. - - ////////////////////////// - // CALCULATE FOR X - - // Randomize a deviation value. - INT32 uiCounterForceDeviationX = PreRandom(uiCounterForceAccuracy); - // Use as a percentage to the ideal counter force. - FLOAT iCounterForceDeviationX = (FLOAT)abs((uiCounterForceDeviationX * iIdealCounterForceX) / 100.0); - // Make sure there's always a little randomness - no one is THAT accurate unless the gun is producing really minimal - // amounts of recoil (or none). - iCounterForceDeviationX = __max(iCounterForceDeviationX, abs(iIdealCounterForceX * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR)); - - // Determine whether we're under- or over-compensating. - // A merc's current offset and skill (in the form of CFA) should give the merc some level of control when determining whether we - // over or under compensate. If a merc can "see" that he's been undercompensating too much, he's much more likely to overcompensate - // and vice versa - INT8 bUpDownX; - if(*dMuzzleOffsetX != 0 && PreRandom(100) <= (100-uiCounterForceAccuracy)) - { //merc realizes he's over/under compensated too much - // Current offset is high so merc is more likely to overcompensate - if(*dMuzzleOffsetX > 0) - bUpDownX = PreRandom(3)?(-1):(1); - // Current offset is low so merc is more likely to undercompensate - else if(*dMuzzleOffsetX < 0) - bUpDownX = PreRandom(3)?(1):(-1); - } - else - { //merc doesn't realize he's over/under compensating - bUpDownX = PreRandom(2)?(1):(-1); - } - iCounterForceDeviationX *= bUpDownX; - - // So now we have the ideal amount, and the randomal deviation from that amount. Let's add them up. - iAppliedCounterForceX = iIdealCounterForceX + iCounterForceDeviationX; - - ////////////////////////// - // CALCULATE FOR Y - - // Randomize a deviation value. - INT32 uiCounterForceDeviationY = PreRandom(uiCounterForceAccuracy); - // Use as a percentage to the ideal counter force. - FLOAT iCounterForceDeviationY = (FLOAT)abs((uiCounterForceDeviationY * iIdealCounterForceY) / 100.0); - // Make sure there's always a little randomness - no one is THAT accurate unless the gun is producing really minimal - // amounts of recoil (or none). - iCounterForceDeviationY = __max(iCounterForceDeviationY, abs(iIdealCounterForceY * gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR)); - - // Determine whether we're under- or over-compensating. - // A merc's current offset and skill (in the form of CFA) should give the merc some level of control when determining whether we - // over or under compensate. If a merc can "see" that he's been undercompensating too much, he's much more likely to overcompensate - // and vice versa - INT8 bUpDownY; - if(*dMuzzleOffsetY != 0 && PreRandom(100) <= (100-uiCounterForceAccuracy)) - { //merc realizes he's over/under compensated too much - // Current offset is high so merc is more likely to overcompensate - if(*dMuzzleOffsetY > 0) - bUpDownY = PreRandom(3)?(-1):(1); - // Current offset is low so merc is more likely to undercompensate - else if(*dMuzzleOffsetY < 0) - bUpDownY = PreRandom(3)?(1):(-1); - } - else - { //merc doesn't realize he's over/under compensating - bUpDownY = PreRandom(2)?(1):(-1); - } - iCounterForceDeviationY *= bUpDownY; - - // So now we have the ideal amount, and the randomal deviation from that amount. Let's add them up. - iAppliedCounterForceY = iIdealCounterForceY + iCounterForceDeviationY; - - ///////////////////////////// - // Use pythagorean to scale this to the max force allowed. - // We want to use pythagorean here so that we calculate the total "vector length" of recoil and compare that to CFM - FLOAT iTotalAppliedCounterForce = sqrt((iAppliedCounterForceX*iAppliedCounterForceX) + (iAppliedCounterForceY*iAppliedCounterForceY)); - if (iTotalAppliedCounterForce > iCounterForceMax) - { - FLOAT iRatio = iTotalAppliedCounterForce / iCounterForceMax; - if (iRatio != 0) + // To do this we don't have to change anything. dIdealChangeX already tells us which direction + // to accelerate in, and also indicates the "IDEAL" amount we need to apply. If that ideal exceeds + // the delta limit, that's fine - it'll be reduced appropriately later in this function anyway. + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) { - iAppliedCounterForceX /= iRatio; - iAppliedCounterForceY /= iRatio; + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Muzzle stationary :: Increasing CF towards target."); } } - // This works because sqrt((iACFX^2)+(iACFY^2)) should result in being less then or equal to CFM + } + else + { + ///////////////// MOVING MUZZLE - // Record how much counter force was applied this time. It will be used for the next few shots until the - // shooter can recalculate. - pShooter->dPrevCounterForceX = iAppliedCounterForceX; - pShooter->dPrevCounterForceY = iAppliedCounterForceY; + // The muzzle is currently in motion, which complicates things. + // Let's find out what exactly we want to do based on the current situation. + if (dMuzzleOffset == 0) + { + // Our muzzle is ON TOP OF the target - but is still in motion. Our only sane option at this + // point is to decelerate muzzle movement. The change, therefore, should attempt to cancel + // out recoil and no more. To do so, calculate our current speed, then invert it. + dIdealDelta = bRecoil + dPrevCounterForce; + dIdealDelta *= -1; + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Muzzle at target but in motion :: Reversing CF."); + } + } + else + { + // If we got here, it means that our muzzle is NOT pointing at the target, and is NOT stationary + // either. This means we're going to have to think carefully about any change we make to CF. + // Is it better to fight recoil right now, or better to let it work? Or maybe even help it + // along. Perhaps muzzle movement needs to be reduced to avoid overshooting the 0,0 target, + // this is what we're going to figure out now. + + // Calculate current muzzle velocity by summing up Recoil and Counter Force. + FLOAT dVelocity = bRecoil + dPrevCounterForce; + + // The sign tells us which way we are going. + INT8 bDirectionMuzzle = (INT8)(dVelocity / abs(dVelocity)); + + // Is the muzzle heading AWAY from the target at the moment? + if (bDirectionMuzzle != bDirectionTarget) + { + // Naturally, moving away from 0,0 is the opposite of what we want. Immediately + // decelerate! We do this simply be reversing speed. + dIdealDelta = dVelocity * (-1); + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Muzzle moving away from target :: Reversing CF."); + } + } + else + { + // Our muzzle is heading towards the 0,0 coordinate. This is good, but we must still be + // careful. If we accelerate too much, we will end up overshooting. If we slow down too + // much, we would take too many bullets to reach the target. Lets figure out what then + // best move is. + + // First lets count the number of steps it would take to stop all muzzle movement. This assumes + // that we will be able to change muzzle movement by a set amount after each bullet (I.E. not + // accounting for mandatory errors). Essentially, we're calculating how fast the muzzle is moving + // right now, and dividing it by the maximum deceleration that can be applied. The result is + // the number of bullets that will elapse before we could stop the muzzle from moving, assuming + // we start decelerating immediately and keep at it until we stop. + UINT32 uiStepsToStopMovement = (INT32)abs((dPrevCounterForce + bRecoil) / dMaxIncrement); + + // Now we count something else entirely: the number of steps it would take to reach target 0,0. + // To do this we actually simulate the remaining bullets in the volley, using current and future + // speed as a guide. For this calculation, we will be assuming that on each step we're going to + // decelerate as much as possible while still remaining in motion. + UINT32 uiStepsToReachTargetWhileDecelerating = 0; + FLOAT dDistanceTraveled = 0; + // We'll be working upwards to make things easy. + dVelocity = abs(dVelocity); + + // Start a loop to measure the distance our muzzle will travel after + // each bullet. With each cycle, we will decrease our speed by the + // maximum allowed amount, and add the distance traveled to a counter. + // We only stop once we've arrived at or overpassed the target 0,0. + // As we go, we'll count the number of steps we've taken. + while (dDistanceTraveled < abs(dMuzzleOffset) ) + { + // Count a step + uiStepsToReachTargetWhileDecelerating++; + + // Reduce speed by one full increment. + dVelocity = dVelocity - dMaxIncrement; + // But, make sure we stay in motion by enforcing a minimum velocity. + // This minimum is equal to the maximum allowed delta. While traveling at this + // speed we can stop muzzle movement in exactly one step. In other words, it + // is the highest speed at which we are simultaneously both in motion and in + // total control of the gun. + dVelocity = __max(dVelocity, dMaxIncrement); + + // Measure the distance we've traveled so far based on the current speed. + dDistanceTraveled += dVelocity; + } + + // We now know how many steps it would take to stop the muzzle from moving, and how + // many steps it will take to reach 0,0 if we decelerate constantly. + + // However, before using these values we need to ask: are we actually intending to + // fire that many bullets? + UINT32 uiRoundsRemaining = uiIntendedBullets - (pShooter->bDoBurst - 1); + BOOLEAN fEnoughBullets = true; + if (uiRoundsRemaining < uiStepsToReachTargetWhileDecelerating) + { + // Setting this flag to false means we don't intend to fire enough bullets + // to use a "smooth and controllable" deceleration as seen above. The flag will + // then signal to the code below that we're in a hurry. As a result, the program + // will force MORE acceleration towards 0,0, increasing the chance of sweeping + // across it before the volley ends. + fEnoughBullets = false; + } + + // We are now ready to compare the two Step Counts. By determining whether it would + // take more steps to stop the muzzle or more steps to reach 0,0, we can decide whether + // acceleration or deceleration are preferable at this time. + + // If it will take longer to stop movement than it would to reach 0,0 (and assuming + // we have enough bullets to reach 0,0 while slowing down...) + if (uiStepsToReachTargetWhileDecelerating <= uiStepsToStopMovement && fEnoughBullets ) + { + // We are already traveling too fast. Even if we decelerate constantly, we will + // reach 0,0 before the muzzle can be fully stopped. Therefore, we have to start + // slowing down immediately. We're going to overshoot the target, but at least lets + // not overshoot too much... + + dIdealDelta = (-1) * bDirectionTarget * __min(dMaxIncrement, abs(dIdealDelta)); + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Going to overshoot target :: Decreasing CF."); + } + } + else + { + // It will take more steps to reach the 0,0 point than it would to stop the muzzle + // from moving. That means we are traveling at a "safe" speed. In other words, we + // can afford at least one more step without decelerating. + // So the question now is - can we afford to actually ACCELERATE at least once, or do + // we need to keep our current speed to avoid overshooting? + + // To do this we'll run basically the same check we did before - simulating muzzle + // movement over the new few bullets. Except now we'll be checking what happens if + // we accelerate once (this turn) and THEN start decelerating. If the check goes ok, + // that means we can afford to accelerate this turn. + + // First, increase the number of steps to stop by two (one extra step to accelerate, + // one extra step to decelerate). + uiStepsToStopMovement += 2; + + // Now let's see how many steps it would take to reach the target with the new speed. + UINT32 uiStepsToReachTarget = 0; + // Calculate current muzzle velocity again + dVelocity = bRecoil + dPrevCounterForce; + // Working only with positives... + dVelocity = abs(dVelocity); + // We're testing to see what happens if we accelerate, so increase velocity by the + // maximum allowed amount. + dVelocity += dMaxIncrement; + dDistanceTraveled = 0; + + // Simulate! + while (dDistanceTraveled < abs(dMuzzleOffset) ) + { + // Count a step + uiStepsToReachTarget++; + // Reduce speed, but keep up moving at least fast enough that we could stop whenever we want. + dVelocity = __max(dVelocity - dMaxIncrement, dMaxIncrement); + // Measure the distance we've traveled so far. + dDistanceTraveled += dVelocity; + } + + // Check whether we have enough bullets to reach the target while decelerating. + // If we don't, we're going to want to force acceleration regardless of what the + // test above says. Accelerating will increase our chance to reach the target AT ALL, + // rather than be forced to stop short of the target for lack of bullets. + BOOLEAN fEnoughBullets = true; + if (uiRoundsRemaining < uiStepsToReachTarget) + { + fEnoughBullets = false; + } + + // Ok, so now lets ask: Are we still moving slowly enough that we can stop the muzzle any time + // before reaching 0,0, if we want to? + if (uiStepsToReachTarget > uiStepsToStopMovement || fEnoughBullets) + { + // Yes! This means that acceleration right now will not prevent us from slowing down later + // and reaching 0,0 with no muzzle movement. Therefore, acceleration is favoured. + + // Create the maximum allowed acceleration in the target's direction. + dIdealDelta = bDirectionTarget * dMaxIncrement; + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Advancing to target :: Increasing CF."); + } + } + else + { + // Accelerating now will inevitably force us to overshoot the target later on. + // Deceleration, as we discovered earlier, is not necessary at this step. + // Therefore, we are content with our current speed, and will likely begin decelerating + // on our next cycle. + + // No change in current muzzle velocity. + dIdealDelta = 0; + if (gGameSettings.fOptions[TOPTION_REPORT_MISS_MARGIN]) + { + ScreenMsg( FONT_ORANGE, MSG_INTERFACE, L"CF CHANGE: Muzzle advancing at acceptable speed :: No change to CF."); + } + } + } + } + } } - iAppliedCounterForceX += bGunRecoilX; - iAppliedCounterForceY += bGunRecoilY; + // Phew! We now have our orders regarding how much velocity change is ideal given + // the muzzle's current velocity and its position compared to the target. + + // However, we're not done yet. First we need to enforce the maximum possible velocity + // change per round, as dictated by the INI file. Then we need to apply a minimum amount + // of shooter error, to add some randomality to this formula - otherwise we're going + // to get shooters who can fill a target with bullets very easily. - // Add a distance ratio - iAppliedCounterForceX *= iDistanceRatio; - iAppliedCounterForceY *= iDistanceRatio; + ///////////////// MAXIMUM DELTA - // Offset the bullet. - *dMuzzleOffsetX += iAppliedCounterForceX; - *dMuzzleOffsetY += iAppliedCounterForceY; + // Lets start by enforcing a maximum change. The acceptable delta is between dMaxIncrement + // and -dMaxIncrement. + FLOAT dDelta = __max(-dMaxIncrement, __min(dIdealDelta, dMaxIncrement)); + INT8 bDeltaSign = (INT8)(dDelta / abs(dDelta)); + ///////////////// MANDATORY ERRORS + // First of all, lets calculate together the forces at work. The more forces are being + // applied, both by the gun and the shooter, the more prone-to-errors we will be. + FLOAT dTotalForcesAtWork = abs(bRecoil) + abs(dPrevCounterForce) + abs(dDelta); + + // Compare this to the shooter's computed strength. Stronger shooters will have much less + // trouble correctly applying their force than weaker ones. + FLOAT dRatio = dTotalForcesAtWork / dCounterForceMax; + + // This value from the INI defines the minimum amount of error we can have, as a percentage + // of the total change we're allowed to make per cycle. + FLOAT dMinCounterForceChangeError = gGameCTHConstants.RECOIL_COUNTER_ACCURACY_MIN_ERROR; + FLOAT dMinError = dMinCounterForceChangeError * dMaxIncrement; + dMinError = __max(0.1f, dMinError); //We always want a little. Always. + + // The maximum error is calculated based on how much change we're trying to make, how + // powerful the forces at work are compared to our strength, and of course how much + // counter-force accuracy our shooter has. + FLOAT dMaxError = dRatio * abs(dDelta); + dMaxError = (dMaxError * (100-uiCounterForceAccuracy)) / 100; + // It can never be less than the minimum enforced error. + dMaxError = __max(dMinError, dMaxError); + + // What's the randomality range? + FLOAT dErrorRange = dMaxError - dMinError; + + // Compute the error! + FLOAT dError = (FLOAT)(Random( (UINT32)(dMaxError * 1000) )) / 1000.0f; + INT8 bErrorSign = (Random(2) ? 1 : (-1)); + + dDelta += (dMinError + dError) * bErrorSign; + if (bDeltaSign == 1) + { + dDelta = __max(0, dDelta); + } + else + { + dDelta = __min(0, dDelta); + } + + return dDelta; } + #if 0 { INT32 cntx, cnty; diff --git a/Tactical/LOS.h b/Tactical/LOS.h index ce7de844..d7456745 100644 --- a/Tactical/LOS.h +++ b/Tactical/LOS.h @@ -67,6 +67,8 @@ INT8 ChanceToGetThrough( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT d INT8 FireBulletGivenTarget( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usHandItem, INT16 sHitBy, BOOLEAN fBuckshot, BOOLEAN fFake ); // HEADROCK HAM 4: Changed the name of one argument to avoid confusion with the new CTH system. INT8 FireBulletGivenTargetNCTH( SOLDIERTYPE * pFirer, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usHandItem, INT16 sApertureRatio, BOOLEAN fBuckshot, BOOLEAN fFake ); +// HEADROCK HAM 5: Function for fragments ejected from an explosion. +INT8 FireFragmentGivenTarget( SOLDIERTYPE * pThrower, FLOAT dStartX, FLOAT dStartY, FLOAT dStartZ, FLOAT dEndX, FLOAT dEndY, FLOAT dEndZ, UINT16 usExplosiveItem ); #define CALC_FROM_ALL_DIRS -1 #define CALC_FROM_WANTED_DIR -2 @@ -223,11 +225,14 @@ void CalcMuzzleSway( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzl FLOAT CalcBulletDeviation( SOLDIERTYPE *pShooter, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, OBJECTTYPE *pWeapon, UINT32 uiRange ); void LimitImpactPointByFacing( SOLDIERTYPE *pShooter, SOLDIERTYPE *pTarget, FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, FLOAT *dEndX, FLOAT *dEndY ); void LimitImpactPointToMaxAperture( FLOAT *dShotOffsetX, FLOAT *dShotOffsetY, FLOAT dDistanceAperture ); -UINT32 CalcCounterForceFrequency(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon); +// HEADROCK HAM 5: Removed and replaced by continuously-variable CF +// UINT32 CalcCounterForceFrequency(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon); FLOAT CalcCounterForceMax(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, UINT8 uiStance = 0); UINT32 CalcCounterForceAccuracy(SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, UINT32 uiRange, BOOLEAN fTracer, bool fAnticipate = false); void CalcPreRecoilOffset( SOLDIERTYPE *pShooter, OBJECTTYPE *pWeapon, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzleOffsetY, UINT32 uiRange ); void CalcRecoilOffset( SOLDIERTYPE *pShooter, FLOAT *dMuzzleOffsetX, FLOAT *dMuzzleOffsetY, OBJECTTYPE *pWeapon, UINT32 uiRange ); +// HEADROCK HAM 5: New function, completely replaces the Counter Force Frequency check. +FLOAT CalcCounterForceChange( SOLDIERTYPE * pShooter, UINT32 uiCounterForceAccuracy, FLOAT dCounterForceMax, FLOAT dMuzzleOffset, INT8 bRecoil, FLOAT dPrevCounterForce, UINT32 uiIntendedBullets ); // HEADROCK HAM 4: Required for shooting mechanic extern INT8 EffectiveMarksmanship( SOLDIERTYPE * pSoldier ); diff --git a/Tactical/Militia Control.cpp b/Tactical/Militia Control.cpp index 68c46167..ac36ac3e 100644 --- a/Tactical/Militia Control.cpp +++ b/Tactical/Militia Control.cpp @@ -1077,8 +1077,8 @@ BOOLEAN CheckIfRadioIsEquipped( void ) if ( GetSoldier( &pSoldier, gusSelectedSoldier ) ) { - bSlot = FindObj( pSoldier, EXTENDEDEAR ); - //bSlot = FindHearingAid(pSoldier); + //bSlot = FindObj( pSoldier, EXTENDEDEAR ); + bSlot = FindHearingAid(pSoldier); //ScreenMsg( FONT_WHITE, MSG_INTERFACE, L"Position: %d", bSlot ); } diff --git a/Tactical/Points.cpp b/Tactical/Points.cpp index 22436577..24fb12e8 100644 --- a/Tactical/Points.cpp +++ b/Tactical/Points.cpp @@ -2692,7 +2692,6 @@ INT16 GetAPsToAutoReload( SOLDIERTYPE * pSoldier ) { // Flugente: check for underbarrel weapons and use that object if necessary pObj = pSoldier->GetUsedWeapon( &(pSoldier->inv[SECONDHANDPOS]) ); - bExcludeSlot = NO_SLOT; bSlot2 = NO_SLOT; diff --git a/Tactical/Rain.cpp b/Tactical/Rain.cpp index 9798c070..ebd04706 100644 --- a/Tactical/Rain.cpp +++ b/Tactical/Rain.cpp @@ -80,10 +80,11 @@ class SOLDIERTYPE; #define DROP_LENGTH_CHANGE_RATE 0.1f #define DROP_LENGTH_RAND 2.0f -#define BASE_DROP_SPEED 7.0f -#define DROP_SPEED_RANGE 3.5f -#define DROP_SPEED_CHANGE_RATE 0.1f -#define DROP_SPEED_RAND 5.0f +// HEADROCK HAM 5 X: Externalized for snow. +FLOAT BASE_DROP_SPEED; +FLOAT DROP_SPEED_RANGE; +FLOAT DROP_SPEED_CHANGE_RATE; +FLOAT DROP_SPEED_RAND; UINT32 guiMaxRainDrops = 79; @@ -203,11 +204,18 @@ void ResetRain() pRainDrops = NULL; } + // Rain + BASE_DROP_SPEED = 7.0f; + DROP_SPEED_RANGE = 3.5f; + DROP_SPEED_CHANGE_RATE = 0.1f; + DROP_SPEED_RAND = 5.0f; + guiCurrMaxAmountOfRainDrops = 0; } void GenerateRainDropsList() { + // HEADROCK HAM 5 XMAS: More snow than rain. guiCurrMaxAmountOfRainDrops = (UINT32)(BASE_MAXIMUM_DROPS) * gbCurrentRainIntensity; pRainDrops = (TRainDrop *)MemAlloc( sizeof( TRainDrop ) * guiCurrMaxAmountOfRainDrops ); @@ -415,6 +423,7 @@ void RenderRainOnSurface() if( !pCurr->fAlive )continue; + // Rain LineDraw( TRUE, (int)pCurr->fpX, (int)pCurr->fpY, (int)pCurr->fpX + (int)pCurr->fpEndRelX, (int)(pCurr->fpY + pCurr->fpEndRelY), sDropsColor, pDestBuf ); } @@ -427,14 +436,19 @@ void GenerateRainMaximums() { fpMinDropAngleOfFalling = 45; fpMaxDropAngleOfFalling = 135; - }else - if( Random( 2 ) ) + } + else { - fpMinDropAngleOfFalling = 20; - fpMaxDropAngleOfFalling = 70; - }else{ - fpMinDropAngleOfFalling = 110; - fpMaxDropAngleOfFalling = 160; + if( Random( 2 ) ) + { + fpMinDropAngleOfFalling = 20; + fpMaxDropAngleOfFalling = 70; + } + else + { + fpMinDropAngleOfFalling = 110; + fpMaxDropAngleOfFalling = 160; + } } fpCurrDropAngleOfFalling = fpMinDropAngleOfFalling + Random( (UINT32)(fpMaxDropAngleOfFalling - fpMinDropAngleOfFalling) ); diff --git a/Tactical/Soldier Create.cpp b/Tactical/Soldier Create.cpp index 25d50e71..cd170627 100644 --- a/Tactical/Soldier Create.cpp +++ b/Tactical/Soldier Create.cpp @@ -74,6 +74,8 @@ #define MAX_PALACE_DISTANCE 20 INT8 bNumSquadleadersInArmy = 0; // added by SANDRO +// HEADROCK HAM 5: Read target coolness by sector +UINT32 gCoolnessBySector[256]; OLD_SOLDIERCREATE_STRUCT_101::OLD_SOLDIERCREATE_STRUCT_101() { initialize(); @@ -3237,8 +3239,8 @@ UINT8 GetLocationModifier( UINT8 ubSoldierClass ) INT16 sSectorX, sSectorY, sSectorZ; #ifdef JA2UB #else - INT8 bTownId; - UINT8 ubPalaceDistance; + //INT8 bTownId; + //UINT8 ubPalaceDistance; #endif BOOLEAN fSuccess; @@ -3372,6 +3374,13 @@ UINT8 GetLocationModifier( UINT8 ubSoldierClass ) break; } #else + // HEADROCK HAM 5: + // The calculation has been replaced with an XML table. + + ubLocationModifier = gCoolnessBySector[SECTOR(sSectorX, sSectorY)]; + + /* + // ignore sSectorZ - treat any underground enemies as if they were on the surface! bTownId = GetTownIdForSector( sSectorX, sSectorY ); @@ -3401,6 +3410,8 @@ UINT8 GetLocationModifier( UINT8 ubSoldierClass ) // adjust for distance from Queen's palace (P3) (0 to +30) ubLocationModifier = ( ( MAX_PALACE_DISTANCE - ubPalaceDistance ) * DIFF_FACTOR_PALACE_DISTANCE ) / MAX_PALACE_DISTANCE; + */ + #endif return( ubLocationModifier ); } diff --git a/Tactical/Tactical_VS2010.vcxproj b/Tactical/Tactical_VS2010.vcxproj index 2843941f..a27e5d28 100644 --- a/Tactical/Tactical_VS2010.vcxproj +++ b/Tactical/Tactical_VS2010.vcxproj @@ -207,8 +207,10 @@ + + diff --git a/Tactical/Tactical_VS2010.vcxproj.filters b/Tactical/Tactical_VS2010.vcxproj.filters index 15521983..e7264ba8 100644 --- a/Tactical/Tactical_VS2010.vcxproj.filters +++ b/Tactical/Tactical_VS2010.vcxproj.filters @@ -611,5 +611,11 @@ Source Files + + Source Files + + + Source Files + \ No newline at end of file diff --git a/Tactical/Weapons.cpp b/Tactical/Weapons.cpp index 4e416dd0..7dda7617 100644 --- a/Tactical/Weapons.cpp +++ b/Tactical/Weapons.cpp @@ -1747,9 +1747,19 @@ BOOLEAN UseGunNCTH( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ) OBJECTTYPE* pA= &(*iter); if ( (*pA)[0]->data.objectStatus >=USABLE) { - INT8 bAmmoReliability = Item[(*pObjAttHand)[0]->data.gun.usGunAmmoItem].bReliability; - uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * - (Item[ iter->usItem ].bReliability + bAmmoReliability), 0); + INT16 ammoReliability = Item[(*pObjAttHand)[0]->data.gun.usGunAmmoItem].bReliability; + // HEADROCK HAM 5: Variable base chance + if ( UsingNewCTHSystem() == true) + { + UINT16 usBaseChance = gGameCTHConstants.BASIC_RELIABILITY_ODDS; + FLOAT dReliabilityRatio = 3.0f * ((FLOAT)usBaseChance / (FLOAT)BASIC_DEPRECIATE_CHANCE); // Compare original odds to new odds. + uiDepreciateTest = usBaseChance + (INT16)( dReliabilityRatio * (Item[ iter->usItem ].bReliability + ammoReliability) ); + uiDepreciateTest = max(0, uiDepreciateTest); + } + else + { + uiDepreciateTest = max(0,BASIC_DEPRECIATE_CHANCE + 3 * (Item[ iter->usItem ].bReliability + ammoReliability)); + } if ( !PreRandom( uiDepreciateTest ) && ( (*pObjAttHand)[0]->data.objectStatus > 1) ) (*pA)[0]->data.objectStatus--; //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"rel =%d ",Item[ iter->usItem ].bReliability ); @@ -2107,8 +2117,18 @@ BOOLEAN UseGunNCTH( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ) //} // Flugente FTW 1: Added a malus to reliability for overheated guns - uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * GetReliability( pObjAttHand ) - iOverheatReliabilityMalus, 0); - + // HEADROCK HAM 5: Variable NCTH base change + if ( UsingNewCTHSystem() == true) + { + UINT16 usBaseChance = gGameCTHConstants.BASIC_RELIABILITY_ODDS; + FLOAT dReliabilityRatio = 3.0f * ((FLOAT)usBaseChance / (FLOAT)BASIC_DEPRECIATE_CHANCE); // Compare original odds to new odds. + uiDepreciateTest = usBaseChance + (INT16)( dReliabilityRatio * GetReliability( &(pSoldier->inv[pSoldier->ubAttackingHand]) - iOverheatReliabilityMalus) ); + uiDepreciateTest = max(0, uiDepreciateTest); + } + else + { + uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * GetReliability( pObjAttHand ) - iOverheatReliabilityMalus, 0); + } if ( !PreRandom( uiDepreciateTest ) && ( (*pObjAttHand)[0]->data.objectStatus > 1) ) { (*pObjAttHand)[0]->data.objectStatus--; @@ -2284,9 +2304,19 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ) OBJECTTYPE* pA= &(*iter); if ( (*pA)[0]->data.objectStatus >=USABLE) { - INT8 bAmmoReliability = Item[(*pObjUsed)[0]->data.gun.usGunAmmoItem].bReliability; - uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * - (Item[ iter->usItem ].bReliability + bAmmoReliability), 0); + INT16 ammoReliability = Item[(*pObjUsed)[0]->data.gun.usGunAmmoItem].bReliability; + // HEADROCK HAM 5: Variable base chance + if ( UsingNewCTHSystem() == true ) + { + UINT16 usBaseChance = gGameCTHConstants.BASIC_RELIABILITY_ODDS; + FLOAT dReliabilityRatio = 3.0f * ((FLOAT)usBaseChance / (FLOAT)BASIC_DEPRECIATE_CHANCE); // Compare original odds to new odds. + uiDepreciateTest = usBaseChance + (INT16)( dReliabilityRatio * (Item[ iter->usItem ].bReliability + ammoReliability) ); + uiDepreciateTest = __max(0, uiDepreciateTest); + } + else + { + uiDepreciateTest = __max(0,BASIC_DEPRECIATE_CHANCE + 3 * (Item[ iter->usItem ].bReliability + ammoReliability)); + } if ( !PreRandom( uiDepreciateTest ) && ( (*pObjUsed)[0]->data.objectStatus > 1) ) (*pA)[0]->data.objectStatus--; //ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, L"rel =%d ",Item[ iter->usItem ].bReliability ); @@ -2675,7 +2705,17 @@ BOOLEAN UseGun( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ) */ // Flugente FTW 1: Added a malus to reliability for overheated guns - uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * ( GetReliability( pObjUsed ) ) - iOverheatReliabilityMalus, 0); + if ( UsingNewCTHSystem() == true ) + { + UINT16 usBaseChance = gGameCTHConstants.BASIC_RELIABILITY_ODDS; + FLOAT dReliabilityRatio = 3.0f * ((FLOAT)usBaseChance / (FLOAT)BASIC_DEPRECIATE_CHANCE); // Compare original odds to new odds. + uiDepreciateTest = usBaseChance + (INT16)( dReliabilityRatio * GetReliability( &(pSoldier->inv[ pSoldier->ubAttackingHand ])) - iOverheatReliabilityMalus); + uiDepreciateTest = max(0, uiDepreciateTest); + } + else + { + uiDepreciateTest = max( BASIC_DEPRECIATE_CHANCE + 3 * ( GetReliability( pObjUsed ) ) - iOverheatReliabilityMalus, 0); + } if ( !PreRandom( uiDepreciateTest ) && ( (*pObjUsed)[0]->data.objectStatus > 1) ) { @@ -4003,14 +4043,27 @@ void WeaponHit( UINT16 usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 s MakeNoise( ubAttackerID, pTargetSoldier->sGridNo, pTargetSoldier->pathing.bLevel, gpWorldLevelData[pTargetSoldier->sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT ); // CALLAHAN START BUGFIX - if ( EXPLOSIVE_GUN( usWeaponIndex ) || AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].explosionSize > 1) + // Provisions for Fragments, which are resulting from a different weapon than the one we are holding in our hand. + UINT8 ubAmmoType = 0; + if ( pObj->usItem == usWeaponIndex ) + { + ubAmmoType = (*pObj)[0]->data.gun.ubGunAmmoType; + } + else + { + // Must be a fragment. + ubAmmoType = Explosive[Item[usWeaponIndex].ubClassIndex].ubFragType; + usWeaponIndex = 1; // Set to default gun. + } + + if ( EXPLOSIVE_GUN( usWeaponIndex ) || AmmoTypes[ubAmmoType].explosionSize > 1) // CALLAHAN END BUGFIX { // Reduce attacker count! //TODO: Madd --- I don't think this code will ever get called for the HE ammo -- the EXPLOSIVE_GUN check filters out regular guns // marke test mag ammo type: pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.ubGunAmmoType // 2cond 'or' added - if ( Item[usWeaponIndex].rocketlauncher || AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].explosionSize > 1 ) + if ( Item[usWeaponIndex].rocketlauncher || AmmoTypes[ubAmmoType].explosionSize > 1 ) { if ( Item[usWeaponIndex].singleshotrocketlauncher ) { @@ -4032,15 +4085,16 @@ void WeaponHit( UINT16 usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 s pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; } } - else if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].explosionSize > 1) + else if ( AmmoTypes[ubAmmoType].explosionSize > 1) { // re-routed the Highexplosive value to define exposion type - IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos ), AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].highExplosive , pTargetSoldier->pathing.bLevel ); + IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos ), AmmoTypes[ubAmmoType].highExplosive , pTargetSoldier->pathing.bLevel ); // pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; } } else // tank cannon { + // HEADROCK HAM 5 TODO: TANK_SHELL! IgniteExplosion( ubAttackerID, sXPos, sYPos, 0, GETWORLDINDEXFROMWORLDCOORDS( sYPos, sXPos ), TANK_SHELL, pTargetSoldier->pathing.bLevel ); } @@ -4088,10 +4142,29 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UIN // Flugente: check for underbarrel weapons and use that object if necessary pObj = MercPtrs[ ubAttackerID ]->GetUsedWeapon( &MercPtrs [ ubAttackerID ]->inv[MercPtrs[ubAttackerID]->ubAttackingHand] ); - if ( fStopped && ubAttackerID != NOBODY ) + // Get attacker + if (ubAttackerID != NOBODY) { pAttacker = MercPtrs[ ubAttackerID ]; + } + // HEADROCK HAM 5: Define differently for fragments + UINT8 ubHitVolume = 0; + UINT8 ubAmmoType = 0; + + if (pBullet->fFragment) + { + ubHitVolume = 1; + ubAmmoType = Explosive[Item[pBullet->fromItem].ubClassIndex].ubFragType; + } + else + { + ubHitVolume = Weapon[usWeaponIndex].ubHitVolume; + ubAmmoType = (*pObj)[0]->data.gun.ubGunAmmoType; + } + + if ( fStopped && ubAttackerID != NOBODY ) + { if ( pAttacker->ubOppNum != NOBODY ) { // if it was another team shooting at someone under our control @@ -4124,11 +4197,11 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UIN if (sZPos > WALL_HEIGHT) { - MakeNoise( ubAttackerID, sGridNo, 1, gpWorldLevelData[sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT ); + MakeNoise( ubAttackerID, sGridNo, 1, gpWorldLevelData[sGridNo].ubTerrainID, ubHitVolume, NOISE_BULLET_IMPACT ); } else { - MakeNoise( ubAttackerID, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, Weapon[ usWeaponIndex ].ubHitVolume, NOISE_BULLET_IMPACT ); + MakeNoise( ubAttackerID, sGridNo, 0, gpWorldLevelData[sGridNo].ubTerrainID, ubHitVolume, NOISE_BULLET_IMPACT ); } } @@ -4146,30 +4219,35 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UIN // Reduce attacker count! DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - end of LAW fire") ); // FreeUpAttacker( ubAttackerID ); - if ( Item[usWeaponIndex].singleshotrocketlauncher ) + // HEADROCK HAM 5: Fragments fired by such weapons should not explode. + if ( pBullet->fFragment == false) { - IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, C1, (INT8)( sZPos >= WALL_HEIGHT ) ); - } - // changed too to use 2 flag to determine - else if ( !Item[usWeaponIndex].singleshotrocketlauncher && Item[usWeaponIndex].rocketlauncher) - //there shouldn't be a way to enter here with an UnderBarrel weapon, so retaining original code :JMich - { - DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("StructureHit: RPG7 item: %d, Ammo: %d",pAttacker->inv[HANDPOS].usItem , pAttacker->inv[HANDPOS][0]->data.gun.usGunAmmoItem ) ); - IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem , (INT8)( sZPos >= WALL_HEIGHT ) ); - - //This is just to make multishot launchers work in semi auto. It's not really a permanent solution because it still doesn't allow autofire, but it will do for now. - OBJECTTYPE * pLaunchable = FindLaunchableAttachment( &(pAttacker->inv[pAttacker->ubAttackingHand ]), pAttacker->inv[pAttacker->ubAttackingHand ].usItem ); - if(pLaunchable){ - pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem = pLaunchable->usItem; - } else { - pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; + if ( Item[usWeaponIndex].singleshotrocketlauncher ) + { + // HEADROCK HAM 5 TODO: C1!!! + IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, C1, (INT8)( sZPos >= WALL_HEIGHT ) ); + } + // changed too to use 2 flag to determine + else if ( !Item[usWeaponIndex].singleshotrocketlauncher && Item[usWeaponIndex].rocketlauncher) + //there shouldn't be a way to enter here with an UnderBarrel weapon, so retaining original code :JMich + { + DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("StructureHit: RPG7 item: %d, Ammo: %d",pAttacker->inv[HANDPOS].usItem , pAttacker->inv[HANDPOS][0]->data.gun.usGunAmmoItem ) ); + IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem , (INT8)( sZPos >= WALL_HEIGHT ) ); + + //This is just to make multishot launchers work in semi auto. It's not really a permanent solution because it still doesn't allow autofire, but it will do for now. + OBJECTTYPE * pLaunchable = FindLaunchableAttachment( &(pAttacker->inv[pAttacker->ubAttackingHand ]), pAttacker->inv[pAttacker->ubAttackingHand ].usItem ); + if(pLaunchable){ + pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem = pLaunchable->usItem; + } else { + pAttacker->inv[pAttacker->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; + } + } + else if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].explosionSize > 1) + { + // re-routed the Highexplosive value to define exposion type + IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, AmmoTypes[ (*pObj)[0]->data.gun.ubGunAmmoType].highExplosive , (INT8)( sZPos >= WALL_HEIGHT ) ); + // pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; } - } - else if ( AmmoTypes[(*pObj)[0]->data.gun.ubGunAmmoType].explosionSize > 1) - { - // re-routed the Highexplosive value to define exposion type - IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, AmmoTypes[ (*pObj)[0]->data.gun.ubGunAmmoType].highExplosive , (INT8)( sZPos >= WALL_HEIGHT ) ); - // pSoldier->inv[pSoldier->ubAttackingHand ][0]->data.gun.usGunAmmoItem = NONE; } // Moved here to make sure ABC stays >0 until everything done @@ -4184,6 +4262,7 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UIN //DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - end of TANK fire") ); //FreeUpAttacker( ubAttackerID ); + // HEADROCK HAM 5 TODO: Tank shell!! IgniteExplosion( ubAttackerID, CenterX( sGridNo ), CenterY( sGridNo ), 0, sGridNo, TANK_SHELL, (INT8)( sZPos >= WALL_HEIGHT ) ); //FreeUpAttacker( (UINT8) ubAttackerID ); @@ -4202,67 +4281,83 @@ void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UIN DamageStructure( pStructure, (UINT8)iImpact, STRUCTURE_DAMAGE_GUNFIRE, sGridNo, sXPos, sYPos, ubAttackerID ); } - switch( Weapon[ usWeaponIndex ].ubWeaponClass ) + // HEADROCK HAM 5: Fragments are not fired from guns, so they need a special case. + if (pBullet->fFragment == false) { - case HANDGUNCLASS: - case RIFLECLASS: - case SHOTGUNCLASS: - case SMGCLASS: - case MGCLASS: + switch( Weapon[ usWeaponIndex ].ubWeaponClass ) + { + case HANDGUNCLASS: + case RIFLECLASS: + case SHOTGUNCLASS: + case SMGCLASS: + case MGCLASS: - // Guy has missed, play random sound - if ( MercPtrs[ ubAttackerID ]->bTeam == gbPlayerNum ) - { - if ( !MercPtrs[ ubAttackerID ]->bDoBurst ) + // Guy has missed, play random sound + if ( MercPtrs[ ubAttackerID ]->bTeam == gbPlayerNum ) { - if ( Random( 40 ) == 0 ) + if ( !MercPtrs[ ubAttackerID ]->bDoBurst ) { - MercPtrs[ ubAttackerID ]->DoMercBattleSound( BATTLE_SOUND_CURSE1 ); + if ( Random( 40 ) == 0 ) + { + MercPtrs[ ubAttackerID ]->DoMercBattleSound( BATTLE_SOUND_CURSE1 ); + } } } - } - //fDoMissForGun = TRUE; - //break; - fDoMissForGun = TRUE; - break; + //fDoMissForGun = TRUE; + //break; + fDoMissForGun = TRUE; + break; - case MONSTERCLASS: + case MONSTERCLASS: - DoSpecialEffectAmmoMiss( ubAttackerID, sGridNo, sXPos, sYPos, sZPos, FALSE, TRUE, iBullet ); - - RemoveBullet( iBullet ); - // DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - monster attack hit structure") ); - // FreeUpAttacker( (UINT8) ubAttackerID ); - - //PlayJA2Sample( SPIT_RICOCHET , RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) ); - break; - - case KNIFECLASS: - - // When it hits the ground, leave on map... - if ( Item[ usWeaponIndex ].usItemClass == IC_THROWING_KNIFE ) - { - // OK, have we hit ground? - if ( usStructureID == INVALID_STRUCTURE_ID ) - { - // Add item - CreateItem( usWeaponIndex, bWeaponStatus, &gTempObject ); - - AddItemToPool( sGridNo, &gTempObject, -1 , 0, 0, -1 ); - - // Make team look for items - NotifySoldiersToLookforItems( ); - } - - if ( !fHitSameStructureAsBefore ) - { - PlayJA2Sample( MISS_KNIFE, RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) ); - } + DoSpecialEffectAmmoMiss( ubAttackerID, sGridNo, sXPos, sYPos, sZPos, FALSE, TRUE, iBullet ); RemoveBullet( iBullet ); - // DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - knife attack hit structure") ); + // DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - monster attack hit structure") ); // FreeUpAttacker( (UINT8) ubAttackerID ); - } + + //PlayJA2Sample( SPIT_RICOCHET , RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) ); + break; + + case KNIFECLASS: + + // When it hits the ground, leave on map... + if ( Item[ usWeaponIndex ].usItemClass == IC_THROWING_KNIFE ) + { + // OK, have we hit ground? + if ( usStructureID == INVALID_STRUCTURE_ID ) + { + // Add item + CreateItem( usWeaponIndex, bWeaponStatus, &gTempObject ); + + AddItemToPool( sGridNo, &gTempObject, -1 , 0, 0, -1 ); + + // Make team look for items + NotifySoldiersToLookforItems( ); + } + + if ( !fHitSameStructureAsBefore ) + { + PlayJA2Sample( MISS_KNIFE, RATE_11025, uiMissVolume, 1, SoundDir( sGridNo ) ); + } + + RemoveBullet( iBullet ); + // DebugMsg( TOPIC_JA2, DBG_LEVEL_3, String("@@@@@@@ Freeing up attacker - knife attack hit structure") ); + // FreeUpAttacker( (UINT8) ubAttackerID ); + } + } + } + else + { + if ( sZPos == 0 ) + { + PlayJA2Sample( MISS_G2 , RATE_11025, 5, 1, SoundDir( sGridNo ) ); + } + else + { + PlayJA2Sample( MISS_1 + Random(8), RATE_11025, 5, 1, SoundDir( sGridNo ) ); + } + RemoveBullet( iBullet ); } if ( fDoMissForGun ) @@ -8442,8 +8537,24 @@ INT32 TotalArmourProtection( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 u return( iTotalProtection ); } -INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iOrigImpact, INT16 sHitBy, UINT8 * pubSpecial ) +// HEADROCK HAM 5: Added argument the bullet! +INT32 BulletImpact( SOLDIERTYPE *pFirer, BULLET *pBullet, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iOrigImpact, INT16 sHitBy, UINT8 * pubSpecial ) { + UINT16 usAttackingWeapon = 0; + INT32 sOrigGridNo = 0; + BOOLEAN fFragment = FALSE; + if (pBullet == NULL) + { + usAttackingWeapon = pFirer->inv[pFirer->ubAttackingHand][0]->data.gun.ubGunAmmoType; + sOrigGridNo = pFirer->sGridNo; + } + else + { + usAttackingWeapon = pBullet->fromItem; + sOrigGridNo = pBullet->sOrigGridNo; + fFragment = pBullet->fFragment; + } + INT32 iImpact, iFluke, iBonus, iImpactForCrits = 0; INT8 bStatLoss = 0; UINT8 ubAmmoType; @@ -8452,17 +8563,21 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat // in MoveBullet. // Set a few things up: - if ( Item[ pFirer->usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE ) + if ( Item[ usAttackingWeapon ].usItemClass == IC_THROWING_KNIFE ) { ubAmmoType = AMMO_KNIFE; } - else + // HEADROCK HAM 5: Added provisions for fragments, which are not fired from the weapon in your hand. + else if ( fFragment == FALSE ) { // Flugente: check for underbarrel weapons and use that object if necessary OBJECTTYPE* pObj = pFirer->GetUsedWeapon( &pFirer->inv[pFirer->ubAttackingHand] ); - ubAmmoType = (*pObj)[0]->data.gun.ubGunAmmoType; } + else + { + ubAmmoType = Explosive[Item[usAttackingWeapon].ubClassIndex].ubFragType; + } if ( TANK( pTarget ) ) { @@ -8569,7 +8684,7 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat } // Sniper trait adds bonus damage per aim click - if (HAS_SKILL_TRAIT( pFirer, SNIPER_NT ) && (pFirer->aiData.bAimTime >= gSkillTraitValues.ubSNDamageBonusFromNumClicks)) + if (HAS_SKILL_TRAIT( pFirer, SNIPER_NT ) && (pFirer->aiData.bAimTime >= gSkillTraitValues.ubSNDamageBonusFromNumClicks) && !fFragment) { iImpact += (INT32)(iImpact * (pFirer->aiData.bAimTime - gSkillTraitValues.ubSNDamageBonusFromNumClicks + 1) * gSkillTraitValues.ubSNDamageBonusPerClick * NUM_SKILL_TRAITS( pFirer, SNIPER_NT ))/100; // +5% per trait } @@ -8593,8 +8708,15 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat else if (pTarget->ubSoldierClass == SOLDIER_CLASS_ELITE && gGameExternalOptions.sEnemyEliteDamageResistance != 0) iImpact -= ((iImpact * gGameExternalOptions.sEnemyEliteDamageResistance) /100); - - iImpact = max( 1, iImpact); + // HEADROCK HAM 5.1: Oh sandro, you rendered zerominimumdamage moot... + if ( AmmoTypes[ubAmmoType].zeroMinimumDamage ) + { + iImpact = __max( 0, iImpact ); + } + else + { + iImpact = __max( 1, iImpact); + } } ////////////////////////////////////////////////////////////////////////////////////// @@ -8622,7 +8744,15 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat case MIKE: // Only 1/2 of the bonus iImpact -= (INT32)(iImpact * gGameExternalOptions.usSpecialNPCStronger / 200); - iImpact = max( 1, iImpact); + // HEADROCK HAM 5.1: Oh sandro, you rendered zerominimumdamage moot... + if ( AmmoTypes[ubAmmoType].zeroMinimumDamage ) + { + iImpact = __max( 0, iImpact ); + } + else + { + iImpact = __max( 1, iImpact); + } break; } } @@ -8631,7 +8761,16 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat //////////////////////////////////////////////////////////////////////////////////// // STOMP traits - Bodybuilding damage resistance if ( gGameOptions.fNewTraitSystem && HAS_SKILL_TRAIT( pTarget, BODYBUILDING_NT ) ) - iImpact = max( 1, (INT32)(iImpact * (100 - gSkillTraitValues.ubBBDamageResistance) / 100)); + // HEADROCK HAM 5.1: Oh sandro, you rendered zerominimumdamage moot... + if ( AmmoTypes[ubAmmoType].zeroMinimumDamage ) + { + iImpact = __max( 0, (INT32)(iImpact * (100 - gSkillTraitValues.ubBBDamageResistance) / 100)); + } + else + { + iImpact = __max( 1, (INT32)(iImpact * (100 - gSkillTraitValues.ubBBDamageResistance) / 100)); + } + //////////////////////////////////////////////////////////////////////////////////// AdjustImpactByHitLocation( iImpact, ubHitLocation, &iImpact, &iImpactForCrits ); @@ -8642,7 +8781,8 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat // is the blow deadly enough for an instant kill? // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. //if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) - if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= Weapon[ pFirer->usAttackingWeapon ].maxdistformessydeath ) + // HEADROCK HAM 5.1: Using usAttackingWeapon + if ( PythSpacesAway( sOrigGridNo, pTarget->sGridNo ) <= Weapon[ usAttackingWeapon ].maxdistformessydeath ) { if (iImpactForCrits > MIN_DAMAGE_FOR_INSTANT_KILL && iImpactForCrits < pTarget->stats.bLife) { @@ -8695,7 +8835,8 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat // since this value is much lower than the others, it only applies at short range... // HEADROCK HAM 3.6: Reattached "Max Distance For Messy Death" tag from the XML! God knows why it wasn't attached when they MADE THAT TAG. //if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_DISTANCE_FOR_MESSY_DEATH || (PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= MAX_BARRETT_DISTANCE_FOR_MESSY_DEATH && pFirer->usAttackingWeapon == BARRETT )) - if ( PythSpacesAway( pFirer->sGridNo, pTarget->sGridNo ) <= Weapon[ pFirer->usAttackingWeapon ].maxdistformessydeath ) + // HEADROCK HAM 5.1: Using usAttackingWeapon + if ( PythSpacesAway( sOrigGridNo, pTarget->sGridNo ) <= Weapon[ usAttackingWeapon ].maxdistformessydeath ) { if (iImpact > MIN_DAMAGE_FOR_INSTANT_KILL && iImpact < pTarget->stats.bLife) { @@ -8741,7 +8882,7 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat if( !IsAutoResolveActive() ) { - if ( AmmoTypes[ubAmmoType].knife && pFirer->aiData.bOppList[ pTarget->ubID ] == SEEN_CURRENTLY ) + if ( AmmoTypes[ubAmmoType].knife && pFirer->aiData.bOppList[ pTarget->ubID ] == SEEN_CURRENTLY && !fFragment ) { // is this a stealth attack? if ( pTarget->aiData.bOppList[ pFirer->ubID ] == NOT_HEARD_OR_SEEN && !CREATURE_OR_BLOODCAT( pTarget ) @@ -8793,10 +8934,18 @@ INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocat if (iImpactForCrits > 0 && iImpactForCrits < pTarget->stats.bLife && !(pTarget->flags.uiStatusFlags & SOLDIER_MONSTER) ) // not to monsters - SANDRO { - UINT32 uiCritChance = (iImpactForCrits / 2) + (pFirer->aiData.bAimTime * 5); + UINT32 uiCritChance = 0; + if (fFragment) + { + uiCritChance = (iImpactForCrits / 2); + } + else + { + uiCritChance = (iImpactForCrits / 2) + (pFirer->aiData.bAimTime * 5); + } BOOLEAN fMaliciousHit = FALSE; // SANDRO - Malicious characters inflict stat loss more often - if ( gGameOptions.fNewTraitSystem && pFirer->ubProfile != NO_PROFILE ) + if ( gGameOptions.fNewTraitSystem && pFirer->ubProfile != NO_PROFILE && !fFragment) { if ( gMercProfiles[ pFirer->ubProfile ].bCharacterTrait == CHAR_TRAIT_MALICIOUS ) { diff --git a/Tactical/Weapons.h b/Tactical/Weapons.h index bb5e0b74..e7c3f7f5 100644 --- a/Tactical/Weapons.h +++ b/Tactical/Weapons.h @@ -358,6 +358,11 @@ typedef struct UINT16 ubDuration; UINT16 ubStartRadius; UINT8 ubMagSize; + BOOLEAN fExplodeOnImpact; // HEADROCK HAM 5: Flag for impact explosives. + UINT16 usNumFragments; // HEADROCK HAM 5.1: Fragmenting Explosive Data + UINT8 ubFragType; + UINT16 ubFragDamage; + UINT16 ubFragRange; } EXPLOSIVETYPE; //GLOBALS @@ -381,7 +386,7 @@ extern BOOLEAN FireWeapon( SOLDIERTYPE *pSoldier , INT32 sTargetGridNo ); extern void WeaponHit( UINT16 usSoldierID, UINT16 usWeaponIndex, INT16 sDamage, INT16 sBreathLoss, UINT16 usDirection, INT16 sXPos, INT16 sYPos, INT16 sZPos, INT16 sRange , UINT8 ubAttackerID, BOOLEAN fHit, UINT8 ubSpecial, UINT8 ubHitLocation ); extern void StructureHit( INT32 iBullet, UINT16 usWeaponIndex, INT16 bWeaponStatus, UINT8 ubAttackerID, UINT16 sXPos, INT16 sYPos, INT16 sZPos, UINT16 usStructureID, INT32 iImpact, BOOLEAN fStopped ); extern void WindowHit( INT32 sGridNo, UINT16 usStructureID, BOOLEAN fBlowWindowSouth, BOOLEAN fLargeForce ); -extern INT32 BulletImpact( SOLDIERTYPE *pFirer, SOLDIERTYPE * pTarget, UINT8 ubHitLocation, INT32 iImpact, INT16 sHitBy, UINT8 * pubSpecial ); +// HEADROCK HAM 5.1: Moved to Bullets.h extern BOOLEAN InRange( SOLDIERTYPE *pSoldier, INT32 sGridNo ); extern void ShotMiss( UINT8 ubAttackerID, INT32 iBullet ); extern UINT32 CalcChanceToHitGun(SOLDIERTYPE *pSoldier, INT32 sGridNo, INT16 ubAimTime, UINT8 ubAimPos ); diff --git a/Tactical/XML.h b/Tactical/XML.h index 9225e7c3..e698ed25 100644 --- a/Tactical/XML.h +++ b/Tactical/XML.h @@ -60,6 +60,8 @@ typedef PARSE_STAGE; #define COMPATIBLEFACEITEMSFILENAME "CompatibleFaceItems.xml" #define MERGESFILENAME "Merges.xml" #define ATTACHMENTCOMBOMERGESFILENAME "AttachmentComboMerges.xml" +// HEADROCK HAM 5: Item Transformations Filename +#define ITEMTRANSFORMATIONSFILENAME "Item_Transformations.xml" #define MAGAZINESFILENAME "Magazines.xml" #define ARMOURSFILENAME "Armours.xml" #define EXPLOSIVESFILENAME "Explosives.xml" @@ -74,6 +76,9 @@ typedef PARSE_STAGE; // CHRISL: #define LOADBEARINGEQUIPMENTFILENAME "LoadBearingEquipment.xml" #define LBEPOCKETFILENAME "Pockets.xml" +// THE_BOB : added for pocket popup definitions +#define LBEPOCKETPOPUPFILENAME "PocketPopups.xml" + #define MERCSTARTINGGEARFILENAME "MercStartingGear.xml" #ifdef JA2UB @@ -161,6 +166,8 @@ typedef PARSE_STAGE; #define FACILITYTYPESFILENAME "Map\\FacilityTypes.xml" // HEADROCK HAM 3.6: Sector Names [2009-07-27] #define SECTORNAMESFILENAME "Map\\SectorNames.xml" +// HEADROCK HAM 5: Coolness by Sector [2011-11-19] +#define COOLNESSBYSECTORFILENAME "Map\\CoolnessBySector.xml" // HEADROCK PROFEX: Merc Profiles [2009-07-27] #define MERCPROFILESFILENAME "MercProfiles.xml" // HEADROCK PROFEX: Merc Opinions [2009-07-27] @@ -267,6 +274,9 @@ extern BOOLEAN WriteMergeStats(); extern BOOLEAN ReadInAttachmentComboMergeStats(STR fileName); extern BOOLEAN WriteAttachmentComboMergeStats(); +// HEADROCK HAM 5: Item Transformation XML reader +extern BOOLEAN ReadInTransformationStats(STR fileName); + extern BOOLEAN ReadInArmourStats(STR fileName); extern BOOLEAN WriteArmourStats(); @@ -277,6 +287,9 @@ extern BOOLEAN WritelbeEquipmentStats(); extern BOOLEAN ReadInLBEPocketStats(STR fileName, BOOLEAN localizedVersion); extern BOOLEAN WriteLBEPocketEquipmentStats(); +// THE_BOB : added for pocket popup definitions +extern BOOLEAN ReadInLBEPocketPopups(STR fileName); + extern BOOLEAN ReadInMercStartingGearStats(STR fileName); extern BOOLEAN WriteMercStartingGearStats(); @@ -391,6 +404,9 @@ extern BOOLEAN ReadInFacilityTypes(STR fileName, BOOLEAN localizedVersion); // HEADROCK HAM 3.6: Customized Sector Names extern BOOLEAN ReadInSectorNames(STR fileName, BOOLEAN localizedVersion, INT8 Level ); +// HEADROCK HAM 5: Coolness by Sectors +extern BOOLEAN ReadInCoolnessBySector(STR fileName ); + // HEADROCK PROFEX: Merc Profiles extern BOOLEAN ReadInMercProfiles(STR fileName, BOOLEAN localizedVersion); diff --git a/Tactical/XML_Explosive.cpp b/Tactical/XML_Explosive.cpp index 2ff644b2..c69673e2 100644 --- a/Tactical/XML_Explosive.cpp +++ b/Tactical/XML_Explosive.cpp @@ -57,7 +57,12 @@ explosiveStartElementHandle(void *userData, const XML_Char *name, const XML_Char strcmp(name, "ubStartRadius") == 0 || strcmp(name, "ubMagSize") == 0 || strcmp(name, "ubDuration") == 0 || - strcmp(name, "ubAnimationID") == 0 )) + strcmp(name, "ubAnimationID") == 0 || + strcmp(name, "fExplodeOnImpact") == 0 ||// HEADROCK HAM 5: Explode on impact flag + strcmp(name, "usNumFragments") == 0 || // HEADROCK HAM 5.1: Fragmenting explosive data + strcmp(name, "ubFragType") == 0 || + strcmp(name, "ubFragDamage") == 0 || + strcmp(name, "ubFragRange") == 0 )) { pData->curElement = ELEMENT_PROPERTY; @@ -159,6 +164,37 @@ explosiveEndElementHandle(void *userData, const XML_Char *name) pData->curElement = ELEMENT; pData->curExplosive.ubDuration = (UINT8) atol(pData->szCharData); } + // HEADROCK HAM 5: Flag for "Explosion on Impact" + else if(strcmp(name, "fExplodeOnImpact") == 0) + { + pData->curElement = ELEMENT; + pData->curExplosive.fExplodeOnImpact = (BOOLEAN) atol(pData->szCharData); + } + + // HEADROCK HAM 5.1: Four tags for Fragmenting Explosives + else if(strcmp(name, "usNumFragments") == 0) + { + pData->curElement = ELEMENT; + pData->curExplosive.usNumFragments = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "ubFragType") == 0) + { + pData->curElement = ELEMENT; + pData->curExplosive.ubFragType = (UINT8) atol(pData->szCharData); + } + + else if(strcmp(name, "ubFragDamage") == 0) + { + pData->curElement = ELEMENT; + pData->curExplosive.ubFragDamage = (UINT16) atol(pData->szCharData); + } + + else if(strcmp(name, "ubFragRange") == 0) + { + pData->curElement = ELEMENT; + pData->curExplosive.ubFragRange = (UINT16) atol(pData->szCharData); + } pData->maxReadDepth--; } @@ -262,6 +298,7 @@ BOOLEAN WriteExplosiveStats() FilePrintf(hFile,"\t\t%d\r\n", Explosive[cnt].ubDuration ); FilePrintf(hFile,"\t\t%d\r\n", Explosive[cnt].ubStartRadius ); FilePrintf(hFile,"\t\t%d\r\n", Explosive[cnt].ubMagSize ); + FilePrintf(hFile,"\t\t%d\r\n", (UINT8)Explosive[cnt].fExplodeOnImpact ); FilePrintf(hFile,"\t\r\n"); } diff --git a/Tactical/XML_ItemAdjustments.cpp b/Tactical/XML_ItemAdjustments.cpp new file mode 100644 index 00000000..e36ee78d --- /dev/null +++ b/Tactical/XML_ItemAdjustments.cpp @@ -0,0 +1,238 @@ +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "overhead.h" + #include "weapons.h" + #include "Debug Control.h" + #include "expat.h" + #include "gamesettings.h" + #include "XML.h" + #include "Item Types.h" +#endif + +struct +{ + PARSE_STAGE curElement; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + TransformInfoStruct curTransform; + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 curResultIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef transformParseData; + +static void XMLCALL +transformStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + transformParseData * pData = (transformParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "TRANSFORMATIONS_LIST") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = ELEMENT_LIST; + + // Reset all transformation indexes to -1, indicating they are "Not Taken" + for (UINT16 x = 0; x < MAXITEMS; x++) + { + Transform[x].usItem = -1; + } + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "TRANSFORM") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = ELEMENT; + + // Reset values + pData->curTransform.usItem = -1; + for (UINT8 x = 0; x < MAX_NUM_TRANSFORMATION_RESULTS; x++) + { + pData->curTransform.usResult[x] = 0; + } + pData->curTransform.usAPCost = 0; + swprintf(pData->curTransform.szMenuRowText, L""); + swprintf(pData->curTransform.szTooltipText, L""); + + pData->maxReadDepth++; //we are not skipping this element + pData->curIndex++; + pData->curResultIndex = 0; + } + else if(pData->curElement == ELEMENT && + (strcmp(name, "usItem") == 0 || + strcmp(name, "usResult") == 0 || + strcmp(name, "usAPCost") == 0 || + strcmp(name, "iBPCost") == 0 || + strcmp(name, "szMenuRowText") == 0 || + strcmp(name, "szTooltipText") == 0)) + { + pData->curElement = ELEMENT_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +transformCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + transformParseData * pData = (transformParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +transformEndElementHandle(void *userData, const XML_Char *name) +{ + transformParseData * pData = (transformParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //we're at the end of an element that we've been reading + { + if(strcmp(name, "TRANSFORMATIONS_LIST") == 0) + { + pData->curElement = ELEMENT_NONE; + } + else if(strcmp(name, "TRANSFORM") == 0) + { + pData->curElement = ELEMENT_LIST; + + if(pData->curIndex < pData->maxArraySize) + { + //DebugMsg(TOPIC_JA2, DBG_LEVEL_3,"MergeStartElementHandle: writing merge to array"); + Transform[pData->curIndex].usItem = pData->curTransform.usItem; //write the merge into the table + for (INT32 x = 0; x < MAX_NUM_TRANSFORMATION_RESULTS; x++) + { + Transform[pData->curIndex].usResult[x] = pData->curTransform.usResult[x]; + } + Transform[pData->curIndex].usAPCost = pData->curTransform.usAPCost; + Transform[pData->curIndex].iBPCost = pData->curTransform.iBPCost; + wcscpy( Transform[pData->curIndex].szMenuRowText, pData->curTransform.szMenuRowText ); + wcscpy( Transform[pData->curIndex].szTooltipText, pData->curTransform.szTooltipText ); + } + } + else if(strcmp(name, "usItem") == 0) + { + pData->curElement = ELEMENT; + INT16 usItem = (INT16) atol(pData->szCharData); + if (usItem <= 0) + { + AssertMsg( 0, String( "Item_Transformations.XML error: Entry #%d missing proper tag!", pData->curIndex ) ); + } + pData->curTransform.usItem = usItem; + } + else if(strcmp(name, "usResult") == 0) + { + pData->curElement = ELEMENT; + pData->curTransform.usResult[pData->curResultIndex] = (UINT16) atol(pData->szCharData); + pData->curResultIndex++; + } + else if(strcmp(name, "usAPCost") == 0) + { + pData->curElement = ELEMENT; + pData->curTransform.usAPCost = (UINT16) atol(pData->szCharData); + pData->curTransform.usAPCost = (UINT16)DynamicAdjustAPConstants(pData->curTransform.usAPCost, pData->curTransform.usAPCost); + } + else if(strcmp(name, "iBPCost") == 0) + { + pData->curElement = ELEMENT; + pData->curTransform.iBPCost = (INT32) atol(pData->szCharData); + } + else if(strcmp(name, "szMenuRowText") == 0) + { + pData->curElement = ELEMENT; + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curTransform.szMenuRowText, sizeof(pData->curTransform.szMenuRowText)/sizeof(pData->curTransform.szMenuRowText[0]) ); + pData->curTransform.szMenuRowText[sizeof(pData->curTransform.szMenuRowText)/sizeof(pData->curTransform.szMenuRowText[0]) - 1] = '\0'; + } + else if(strcmp(name, "szTooltipText") == 0) + { + pData->curElement = ELEMENT; + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curTransform.szTooltipText, sizeof(pData->curTransform.szTooltipText)/sizeof(pData->curTransform.szTooltipText[0]) ); + pData->curTransform.szTooltipText[sizeof(pData->curTransform.szTooltipText)/sizeof(pData->curTransform.szTooltipText[0]) - 1] = '\0'; + } + + pData->maxReadDepth--; + } + + pData->currentDepth--; +} + + + + +BOOLEAN ReadInTransformationStats(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + transformParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading Item_Transformations.xml" ); + + // Open merges file + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, transformStartElementHandle, transformEndElementHandle); + XML_SetCharacterDataHandler(parser, transformCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + pData.maxArraySize = MAXITEMS; + pData.curIndex = -1; + + XML_SetUserData(parser, &pData); + + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in Item_Transformations.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + return( TRUE ); +} + diff --git a/Tactical/XML_LBEPocketPopup.cpp b/Tactical/XML_LBEPocketPopup.cpp new file mode 100644 index 00000000..a9462abe --- /dev/null +++ b/Tactical/XML_LBEPocketPopup.cpp @@ -0,0 +1,496 @@ +#ifdef PRECOMPILEDHEADERS + #include "Tactical All.h" +#else + #include "sgp.h" + #include "popup_class.h" + #include "popup_definition.h" + #include "Debug Control.h" + #include "expat.h" + #include "XML.h" + #include "GameSettings.h" +#endif + +// namespace'd because of name collision with POPUP class def +namespace POPUP_PARSE { + enum + { + OUTSIDE_POCKET_LIST = 0, + POCKET_LIST, + + POCKET, + POCKET_PROPERTY, // for reading pocket id + + POPUP, + POPUP_PROPERTY, // unused + // for root-level popup + OPTION, + OPTION_PROPERTY, + GENERATOR, + GENERATOR_PROPERTY, + + SUBMENU, + SUBMENU_PROPERTY, + // for submenus + SUBMENU_OPTION, + SUBMENU_OPTION_PROPERTY, + SUBMENU_GENERATOR, + SUBMENU_GENERATOR_PROPERTY + } + typedef POPUP_PARSE_STAGE; +} + + +struct +{ + POPUP_PARSE::POPUP_PARSE_STAGE curElement; + UINT8 curSubPopupLevel; + + CHAR8 szCharData[MAX_CHAR_DATA_LENGTH+1]; + + // popupDef and its pocket number + UINT8 curPocketId; + popupDef *curPocketPopup; + + // for reading in options to popups and subPopups + //popupDefOption *curPocketPopupOption; // not really used + WCHAR curPocketPopupOptionName[128]; + UINT16 curPocketPopupOptionCallback; + UINT16 curPocketPopupOptionAvail; + + // for reading in subPopups + popupDefSubPopupOption *curPocketSubPopupOption[POPUP_MAX_SUB_POPUPS]; + WCHAR curPocketSubPopupOptionName[POPUP_MAX_SUB_POPUPS][128]; + + // for reading in content generator references + popupDefContentGenerator *curPocketPopupGenerator; // not really used + UINT16 curPocketPopupGeneratorId; + + popupDef * curArray; + + UINT32 maxArraySize; + UINT32 curIndex; + UINT32 currentDepth; + UINT32 maxReadDepth; +} +typedef pocketPopupParseData; + +// maps generator name strings found in XML to generator IDs used by the function that binds them +static UINT16 mapGeneratorNameToId( CHAR8 * name ){ + + if( strcmp(name,"dummy") == 0 ){ + return popupGenerators::dummy; + } else if( strcmp(name,"addArmor") == 0 ){ + return popupGenerators::addArmor; + } else if( strcmp(name,"addLBE") == 0 ){ + return popupGenerators::addLBE; + } else if( strcmp(name,"addWeapons") == 0 ){ + return popupGenerators::addWeapons; + } else if( strcmp(name,"addWeaponGroups") == 0 ){ + return popupGenerators::addWeaponGroups; + } else if( strcmp(name,"addGrenades") == 0 ){ + return popupGenerators::addGrenades; + } else if( strcmp(name,"addBombs") == 0 ){ + return popupGenerators::addBombs; + } else if( strcmp(name,"addFaceGear") == 0 ){ + return popupGenerators::addFaceGear; + } else if( strcmp(name,"addAmmo") == 0 ){ + return popupGenerators::addAmmo; + } else if( strcmp(name,"addRifleGrenades") == 0 ){ + return popupGenerators::addRifleGrenades; + } else if( strcmp(name,"addRocketAmmo") == 0 ){ + return popupGenerators::addRocketAmmo; + } else if( strcmp(name,"addMisc") == 0 ){ + return popupGenerators::addMisc; + } else if( strcmp(name,"addKits") == 0 ){ + return popupGenerators::addKits; + } else return 0; // includes 'none' + +} + +// maps option callback name strings found in XML to callback IDs used by the function that binds them +static UINT16 mapCallbackNameToId( CHAR8 * name ){ + + if( strcmp(name,"dummy") == 0 ){ + return 1; + } else return 0; // includes 'none' + +} + +// maps option availability check function name strings found in XML to their IDs used by the function that binds them +static UINT16 mapAvailNameToId( CHAR8 * name ){ + + if( strcmp(name,"dummy") == 0 ){ + return 1; + } else return 0; // includes 'none' + +} + +static void XMLCALL +pocketPopupStartElementHandle(void *userData, const XML_Char *name, const XML_Char **atts) +{ + pocketPopupParseData * pData = (pocketPopupParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) //are we reading this element? + { + if(strcmp(name, "POCKETPOPUPS") == 0 && pData->curElement == ELEMENT_NONE) + { + pData->curElement = POPUP_PARSE::POCKET_LIST; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(strcmp(name, "POCKET") == 0 && pData->curElement == ELEMENT_LIST) + { + pData->curElement = POPUP_PARSE::POCKET; + + pData->maxReadDepth++; //we are not skipping this element + // TODO: assert if there's no dangling subpopup here, broken XML might cause trouble + pData->curSubPopupLevel = 0; // we're in a new pocket now, so reset the subpopup level + } + else if(pData->curElement == POPUP_PARSE::POCKET && + (strcmp(name, "pIndex") == 0 )) + { + pData->curElement = POPUP_PARSE::POCKET_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::POCKET && + (strcmp(name, "popup") == 0 )) + { + pData->curElement = POPUP_PARSE::POPUP; + + pData->maxReadDepth++; //we are not skipping this element + pData->curPocketPopup = new popupDef(); + } + else if(pData->curElement == POPUP_PARSE::POPUP && // popup options (not in submenu) + (strcmp(name, "option") == 0 )) + { + pData->curElement = POPUP_PARSE::OPTION; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::OPTION && // popup option attributes (not in submenu) + ( strcmp(name, "name") == 0 + || strcmp(name, "action") == 0 + || strcmp(name, "availCheck") == 0 )) + { + pData->curElement = POPUP_PARSE::OPTION_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::POPUP && // content generators (not in submenu) + (strcmp(name, "generator") == 0 )) + { + pData->curElement = POPUP_PARSE::GENERATOR; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::GENERATOR && // content generator attributes (not in submenu) + ( strcmp(name, "id") == 0 ) ) + { + pData->curElement = POPUP_PARSE::GENERATOR_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::POPUP && // submenu (first) + (strcmp(name, "subMenu") == 0 )) + { + pData->curElement = POPUP_PARSE::SUBMENU; + + // TODO: assert that this is the first submenu + pData->curSubPopupLevel = 1; // we're still at popup level so this must be the first subpopup + pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ] = new popupDefSubPopupOption(); + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU && // submenu (deep) + (strcmp(name, "subMenu") == 0 )) + { + pData->curElement = POPUP_PARSE::SUBMENU; + + pData->curSubPopupLevel++; + pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ] = new popupDefSubPopupOption(); + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU && // submenu attributes + ( strcmp(name, "name") == 0 ) ) + { + pData->curElement = POPUP_PARSE::SUBMENU_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU && // popup options (submenu) + (strcmp(name, "option") == 0 )) + { + pData->curElement = POPUP_PARSE::SUBMENU_OPTION; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU_OPTION && // popup option attributes (submenu) + ( strcmp(name, "name") == 0 + || strcmp(name, "action") == 0 + || strcmp(name, "availCheck") == 0 )) + { + pData->curElement = POPUP_PARSE::SUBMENU_OPTION_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU && // content generators (submenu) + (strcmp(name, "generator") == 0 )) + { + pData->curElement = POPUP_PARSE::SUBMENU_GENERATOR; + + pData->maxReadDepth++; //we are not skipping this element + } + else if(pData->curElement == POPUP_PARSE::SUBMENU_GENERATOR && // content generator attributes (submenu) + ( strcmp(name, "id") == 0 ) ) + { + pData->curElement = POPUP_PARSE::SUBMENU_GENERATOR_PROPERTY; + + pData->maxReadDepth++; //we are not skipping this element + } + + + pData->szCharData[0] = '\0'; + } + + pData->currentDepth++; + +} + +static void XMLCALL +pocketPopupCharacterDataHandle(void *userData, const XML_Char *str, int len) +{ + pocketPopupParseData * pData = (pocketPopupParseData *)userData; + + if( (pData->currentDepth <= pData->maxReadDepth) && + (strlen(pData->szCharData) < MAX_CHAR_DATA_LENGTH) + ){ + strncat(pData->szCharData,str,__min((unsigned int)len,MAX_CHAR_DATA_LENGTH-strlen(pData->szCharData))); + } +} + + +static void XMLCALL +pocketPopupEndElementHandle(void *userData, const XML_Char *name) +{ + pocketPopupParseData * pData = (pocketPopupParseData *)userData; + + if(pData->currentDepth <= pData->maxReadDepth) + { + if(strcmp(name, "POCKETPOPUPS") == 0) + { + pData->curElement = POPUP_PARSE::OUTSIDE_POCKET_LIST; + } + else if(strcmp(name, "POCKET") == 0) + { + pData->curElement = POPUP_PARSE::POCKET_LIST; + + // done with the pocket + // nothing to do because we already saved the popup when closing tag + + } + else if(strcmp(name, "pIndex") == 0) + { + pData->curElement = POPUP_PARSE::POCKET; + pData->curPocketId = (UINT8) atol(pData->szCharData); // got pocket index + } + else if(strcmp(name, "popup") == 0) + { + pData->curElement = POPUP_PARSE::POCKET; + // done with the popup definition + LBEPocketPopup[ pData->curPocketId ] = *pData->curPocketPopup; // place the popup definition in pocket popup index + } + else if(strcmp(name, "subMenu") == 0) + { + + // done with the subpopup definition + + // rename the current option, we should've collected a name for it by now + pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ]->rename( new std::wstring( pData->curPocketSubPopupOptionName[ pData->curSubPopupLevel-1 ] ) ); + + if( pData->curSubPopupLevel == 1 ){ // at first submenu level, add the current menu to the base popup + pData->curElement = POPUP_PARSE::POPUP; + + pData->curPocketPopup->addSubPopup( pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ] ); + pData->curSubPopupLevel = 0; + } else { // deep in submenu tree, add the current submenu to the one above + pData->curElement = POPUP_PARSE::SUBMENU; + + pData->curPocketSubPopupOption[ pData->curSubPopupLevel - 2 ]->getSubDef()->addSubPopup( pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ] ); + pData->curSubPopupLevel--; + } + + } + else if( pData->curElement == POPUP_PARSE::OPTION && strcmp(name, "option") == 0) // option (popup) + { + pData->curElement = POPUP_PARSE::POPUP; + // done with the option + pData->curPocketPopup->addOption( new std::wstring( pData->curPocketPopupOptionName ), pData->curPocketPopupOptionCallback, pData->curPocketPopupOptionAvail ); + } + else if( pData->curElement == POPUP_PARSE::SUBMENU_OPTION && strcmp(name, "option") == 0) // option (sub-popup) + { + pData->curElement = POPUP_PARSE::SUBMENU; + // done with the option + pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ]->getSubDef()->addOption( new std::wstring( pData->curPocketPopupOptionName ), pData->curPocketPopupOptionCallback, pData->curPocketPopupOptionAvail ); + } + else if( pData->curElement == POPUP_PARSE::GENERATOR && strcmp(name, "generator") == 0) // generator (popup) + { + pData->curElement = POPUP_PARSE::POPUP; + // done with the generator + pData->curPocketPopup->addGenerator( pData->curPocketPopupGeneratorId ); + } + else if( pData->curElement == POPUP_PARSE::SUBMENU_GENERATOR && strcmp(name, "generator") == 0) // generator (sub-popup) + { + pData->curElement = POPUP_PARSE::SUBMENU; + // done with the generator + pData->curPocketSubPopupOption[ pData->curSubPopupLevel-1 ]->getSubDef()->addGenerator( pData->curPocketPopupGeneratorId ); + } + else if(strcmp(name, "name") == 0) + { + switch( pData->curElement ){ + case POPUP_PARSE::OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::OPTION; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curPocketPopupOptionName, sizeof(pData->curPocketPopupOptionName)/sizeof(pData->curPocketPopupOptionName[0]) ); + pData->curPocketPopupOptionName[sizeof(pData->curPocketPopupOptionName)/sizeof(pData->curPocketPopupOptionName[0]) - 1] = '\0'; + + break; + + case POPUP_PARSE::SUBMENU_OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::SUBMENU_OPTION; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curPocketPopupOptionName, sizeof(pData->curPocketPopupOptionName)/sizeof(pData->curPocketPopupOptionName[0]) ); + pData->curPocketPopupOptionName[sizeof(pData->curPocketPopupOptionName)/sizeof(pData->curPocketPopupOptionName[0]) - 1] = '\0'; + + break; + + case POPUP_PARSE::SUBMENU_PROPERTY: + pData->curElement = POPUP_PARSE::SUBMENU; + + MultiByteToWideChar( CP_UTF8, 0, pData->szCharData, -1, pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1], sizeof(pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1])/sizeof(pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1][0]) ); + pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1][sizeof(pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1])/sizeof(pData->curPocketSubPopupOptionName[pData->curSubPopupLevel-1][0]) - 1] = '\0'; + + break; + + } + } + else if(strcmp(name, "action") == 0) + { + switch( pData->curElement ){ + case POPUP_PARSE::OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::OPTION; + break; + + case POPUP_PARSE::SUBMENU_OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::SUBMENU_OPTION; + break; + } + + pData->curPocketPopupOptionCallback = mapCallbackNameToId(pData->szCharData); + } + else if(strcmp(name, "availCheck") == 0) + { + switch( pData->curElement ){ + case POPUP_PARSE::OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::OPTION; + break; + + case POPUP_PARSE::SUBMENU_OPTION_PROPERTY: + pData->curElement = POPUP_PARSE::SUBMENU_OPTION; + break; + } + + pData->curPocketPopupOptionAvail = mapAvailNameToId(pData->szCharData); + } + else if(strcmp(name, "id") == 0) + { + switch( pData->curElement ){ + case POPUP_PARSE::GENERATOR_PROPERTY: + pData->curElement = POPUP_PARSE::GENERATOR; + break; + + case POPUP_PARSE::SUBMENU_GENERATOR_PROPERTY: + pData->curElement = POPUP_PARSE::SUBMENU_GENERATOR; + break; + } + + pData->curPocketPopupGeneratorId = mapGeneratorNameToId(pData->szCharData); + } + + pData->maxReadDepth--; + } + pData->currentDepth--; +} + + +BOOLEAN ReadInLBEPocketPopups(STR fileName) +{ + HWFILE hFile; + UINT32 uiBytesRead; + UINT32 uiFSize; + CHAR8 * lpcBuffer; + XML_Parser parser = XML_ParserCreate(NULL); + + pocketPopupParseData pData; + + DebugMsg(TOPIC_JA2, DBG_LEVEL_3, "Loading pocketPopups.xml" ); + + hFile = FileOpen( fileName, FILE_ACCESS_READ, FALSE ); + if ( !hFile ) + return( FALSE ); + + uiFSize = FileGetSize(hFile); + lpcBuffer = (CHAR8 *) MemAlloc(uiFSize+1); + + //Read in block + if ( !FileRead( hFile, lpcBuffer, uiFSize, &uiBytesRead ) ) + { + MemFree(lpcBuffer); + return( FALSE ); + } + + lpcBuffer[uiFSize] = 0; //add a null terminator + + FileClose( hFile ); + + + XML_SetElementHandler(parser, pocketPopupStartElementHandle, pocketPopupEndElementHandle); + XML_SetCharacterDataHandler(parser, pocketPopupCharacterDataHandle); + + + memset(&pData,0,sizeof(pData)); + XML_SetUserData(parser, &pData); + + XML_SetUserData(parser, &pData); + + if(!XML_Parse(parser, lpcBuffer, uiFSize, TRUE)) + { + CHAR8 errorBuf[511]; + + sprintf(errorBuf, "XML Parser Error in Pocket.xml: %s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + LiveMessage(errorBuf); + + MemFree(lpcBuffer); + return FALSE; + } + + /* + // dummy popup + + popupDef* popup = new popupDef(); + popup->addOption(new std::wstring(L"Option one"),NULL,NULL); + popup->addOption(new std::wstring(L"Option two"),NULL,NULL); + popup->addOption(new std::wstring(L"Option three"),NULL,NULL); + + LBEPocketPopup[5] = *popup; + */ + + MemFree(lpcBuffer); + + + XML_ParserFree(parser); + + + return( TRUE ); +} \ No newline at end of file diff --git a/Tactical/bullets.cpp b/Tactical/bullets.cpp index 2b83f7b1..8d0c297b 100644 --- a/Tactical/bullets.cpp +++ b/Tactical/bullets.cpp @@ -31,7 +31,8 @@ #endif // Defines -#define NUM_BULLET_SLOTS 50 +// HEADROCK HAM 5: Increasing... with the hope of making spectacular fragmenting explosives. +#define NUM_BULLET_SLOTS 200 // GLOBAL FOR FACES LISTING @@ -105,7 +106,11 @@ INT32 CreateBullet( UINT8 ubFirerID, BOOLEAN fFake, UINT16 usFlags,UINT16 fromIt { pBullet->fReal = TRUE; // gBullets[ iBullet ].pFirer->bBulletsLeft++; - gTacticalStatus.ubAttackBusyCount++; + // HEADROCK HAM 5: Do not create for explosives. + if (!(Item[fromItem].usItemClass & IC_EXPLOSV)) + { + gTacticalStatus.ubAttackBusyCount++; + } DebugAttackBusy( String( "Creating a new bullet for %d. ABC now %d\n", ubFirerID, gTacticalStatus.ubAttackBusyCount) ); } @@ -201,7 +206,11 @@ void RemoveBullet( INT32 iBullet ) // DebugAttackBusy( String( "Deleting a bullet for %d. Total count now %d\n", gBullets[ iBullet].ubFirerID, gBullets[ iBullet ].pFirer->bBulletsLeft) ); // Nah, just decrement the attack busy count and be done with it DebugAttackBusy( String( "Deleting a bullet for %d.\n", gBullets[ iBullet].ubFirerID ) ); - ReduceAttackBusyCount( ); + // HEADROCK HAM 5: Fragments do not need reducing. + if (gBullets[iBullet].fFragment == false) + { + ReduceAttackBusyCount( ); + } // if ( gBullets[ iBullet ].usFlags & ( BULLET_FLAG_KNIFE ) ) // { @@ -233,7 +242,8 @@ void RemoveBullet( INT32 iBullet ) void LocateBullet( INT32 iBulletIndex ) { - if ( gGameSettings.fOptions[ TOPTION_SHOW_MISSES ] ) + // HEADROCK HAM 5: Do not track fragments. There are too many of them. + if ( gGameSettings.fOptions[ TOPTION_SHOW_MISSES ] && gBullets[ iBulletIndex ].fFragment == false) { // Check if a bad guy fired! if ( gBullets[ iBulletIndex ].ubFirerID != NOBODY ) @@ -353,6 +363,7 @@ void UpdateBullets( ) ManLooksForOtherTeams(gBullets[ uiCount ].pFirer); } */ + else { pNode = AddStructToTail( gBullets[ uiCount ].sGridNo, BULLETTILE1 ); @@ -364,7 +375,8 @@ void UpdateBullets( ) pNode->sRelativeZ = (INT16) CONVERT_HEIGHTUNITS_TO_PIXELS( FIXEDPT_TO_INT32( gBullets[ uiCount ].qCurrZ ) ); //afp-start - add new tail /tracer - if (gGameSettings.fOptions[TOPTION_ALTERNATE_BULLET_GRAPHICS]) + // HEADROCK HAM 5: No tail for fragments. + if (gGameSettings.fOptions[TOPTION_ALTERNATE_BULLET_GRAPHICS] && gBullets[ uiCount ].fFragment == false) { if ((lastX != 0) || (lastY != 0)) { diff --git a/TacticalAI/AIMain.cpp b/TacticalAI/AIMain.cpp index f93c28b6..9d5ca392 100644 --- a/TacticalAI/AIMain.cpp +++ b/TacticalAI/AIMain.cpp @@ -2095,6 +2095,8 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) // WANNE.TANK: Choose cannon or rocket UINT16 usHandItem = pSoldier->inv[HANDPOS].usItem; + INT8 bSlot; + if (TANK(pSoldier)) { // No cannon selected to fire @@ -2790,6 +2792,16 @@ INT8 ExecuteAction(SOLDIERTYPE *pSoldier) break; ///////////////////////////////////////////////////////////// + case AI_ACTION_RELOAD_GUN: + bSlot = FindAmmoToReload( pSoldier, pSoldier->aiData.usActionData, NO_SLOT ); + if(bSlot != NO_SLOT) + { + ReloadGun( pSoldier, &(pSoldier->inv[pSoldier->aiData.usActionData]), &(pSoldier->inv[bSlot]) ); + ActionDone( pSoldier ); + } + break; + + default: #ifdef BETAVERSION NumMessage("ExecuteAction - Illegal action type = ",pSoldier->aiData.bAction); diff --git a/TacticalAI/AIUtils.cpp b/TacticalAI/AIUtils.cpp index c67dd728..dc3179ff 100644 --- a/TacticalAI/AIUtils.cpp +++ b/TacticalAI/AIUtils.cpp @@ -1726,31 +1726,20 @@ BOOLEAN InWaterGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ) // smoke if (gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_SMOKE) { - return( TRUE ); + return TRUE; } - // tear/mustard gas - if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 - return(TRUE); - if ( gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_BURNABLEGAS ) - { - return( TRUE ); - } - - return(FALSE); + return InGas( pSoldier, sGridNo ); } BOOLEAN InGasOrSmoke( SOLDIERTYPE *pSoldier, INT32 sGridNo ) { - // smoke/tear/mustard gas - if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_SMOKE|MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 230909 - return(TRUE); - if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS ) - { - return( TRUE ); - } - return(FALSE); + // smoke + if (gpWorldLevelData[sGridNo].ubExtFlags[ pSoldier->pathing.bLevel ] & MAPELEMENT_EXT_SMOKE) + return TRUE; + + return InGas(pSoldier,sGridNo); } @@ -1761,25 +1750,28 @@ INT16 InWaterOrGas(SOLDIERTYPE *pSoldier, INT32 sGridNo) return(TRUE); } - // tear/mustard gas - if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 - return(TRUE); - if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS ) - { - return( TRUE ); - } - - return(FALSE); + return (INT16)InGas( pSoldier, sGridNo ); } BOOLEAN InGas( SOLDIERTYPE *pSoldier, INT32 sGridNo ) -{ - // tear/mustard gas - if((gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 - return(TRUE); - if ( gpWorldLevelData[sGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS ) - { - return( TRUE ); +{//WarmSteel - One square away from gas is still considered in gas, because it could expand any moment. + //Note: this only works for gas that expands with one tile, but hey it's better than nothing! + int iNeighbourGridNo; + for(int iDir = 0; iDir < NUM_WORLD_DIRECTIONS; ++iDir) + { + iNeighbourGridNo = sGridNo + DirectionInc(iDir); + if(!TileIsOutOfBounds(iNeighbourGridNo)) + { + // tear/mustard gas + if((gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & (MAPELEMENT_EXT_TEARGAS|MAPELEMENT_EXT_MUSTARDGAS)) && !DoesSoldierWearGasMask(pSoldier))//dnl ch40 200909 + { + return(TRUE); + } + if ( gpWorldLevelData[iNeighbourGridNo].ubExtFlags[pSoldier->pathing.bLevel] & MAPELEMENT_EXT_BURNABLEGAS ) + { + return( TRUE ); + } + } } return(FALSE); @@ -2249,6 +2241,7 @@ INT32 CalcManThreatValue( SOLDIERTYPE *pEnemy, INT32 sMyGrid, UINT8 ubReduceForC INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo) { + BOOL OppPosKnown = FALSE; if ( CREATURE_OR_BLOODCAT( pSoldier ) ) { if ( pSoldier->aiData.bAlertStatus == STATUS_BLACK ) @@ -2269,6 +2262,19 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo) *pusFromGridNo = pSoldier->aiData.sPatrolGrid[0]; } + //Do we know about any opponent? + for(UINT16 oppID = 0; oppID < MAX_NUM_SOLDIERS; oppID++) + { + if ( pSoldier->aiData.bOppList[oppID] != NOT_HEARD_OR_SEEN && gbPublicOpplist[pSoldier->bTeam][oppID] != NOT_HEARD_OR_SEEN) + { + OppPosKnown = TRUE; + break; + } + } + + //TODO: Externalize if people want? + BOOL fLessRestrictiveRoaming = TRUE; + switch (pSoldier->aiData.bOrders) { // JA2 GOLD: give non-NPCs a 5 tile roam range for cover in combat when being shot at @@ -2281,16 +2287,68 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo) return( 5 ); } case ONGUARD: return( 5 ); - case CLOSEPATROL: return( 15 ); - case RNDPTPATROL: - case POINTPATROL: return(10 ); // from nextPatrolGrid, not whereIWas - case FARPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) + case CLOSEPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) { - return( 25 ); + return( 5 ); } else { - return( 50 ); + if(!OppPosKnown || !fLessRestrictiveRoaming) + { + return( 15 ); + } + else + { + return( 30 ); + //return( MAX_ROAMING_RANGE ); + } + } + case POINTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) + { + return( 10 ); + } + else + { + if(!OppPosKnown || !fLessRestrictiveRoaming) + { + return( 20 ); + } + else + { + return( 40 ); + //return( MAX_ROAMING_RANGE ); + } + } // from nextPatrolGrid, not whereIWas + case RNDPTPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) + { + return( 10 ); + } + else + { + if(!OppPosKnown || !fLessRestrictiveRoaming) + { + return( 20 ); + } + else + { + //return( 40 ); + return( MAX_ROAMING_RANGE ); + } + }// from nextPatrolGrid, not whereIWas + case FARPATROL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) + { + return( 15 ); + } + else + { + if(!OppPosKnown || !fLessRestrictiveRoaming) + { + return( 30 ); + } + else + { + return( MAX_ROAMING_RANGE ); + } } case ONCALL: if (pSoldier->aiData.bAlertStatus < STATUS_RED) { @@ -2298,7 +2356,15 @@ INT16 RoamingRange(SOLDIERTYPE *pSoldier, INT32 * pusFromGridNo) } else { - return( 30 ); + if(!OppPosKnown || !fLessRestrictiveRoaming) + { + return( 30 ); + } + else + { + //return(50); + return( MAX_ROAMING_RANGE ); + } } case SEEKENEMY: *pusFromGridNo = pSoldier->sGridNo; // from current position! return(MAX_ROAMING_RANGE); diff --git a/TacticalAI/Attacks.cpp b/TacticalAI/Attacks.cpp index ec8b7df5..15d15112 100644 --- a/TacticalAI/Attacks.cpp +++ b/TacticalAI/Attacks.cpp @@ -349,7 +349,7 @@ void CalcBestShot(SOLDIERTYPE *pSoldier, ATTACKTYPE *pBestShot, BOOLEAN shootUns continue; // next opponent // calculate chance to REALLY hit: shoot accurately AND get past cover - ubChanceToReallyHit = (ubBestChanceToHit * ubChanceToGetThrough) / 100; + ubChanceToReallyHit = (UINT8)ceil((ubBestChanceToHit * ubChanceToGetThrough) / 100.0f); // if we can't REALLY hit at all if (ubChanceToReallyHit == 0) diff --git a/TacticalAI/DecideAction.cpp b/TacticalAI/DecideAction.cpp index 8ccc2099..2c65449f 100644 --- a/TacticalAI/DecideAction.cpp +++ b/TacticalAI/DecideAction.cpp @@ -2100,7 +2100,7 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) DebugMsg (TOPIC_JA2,DBG_LEVEL_3,String("DecideActionRed: soldier orders = %d",pSoldier->aiData.bOrders)); // if we have absolutely no action points, we can't do a thing under RED! - if (!pSoldier->bActionPoints) + if ( pSoldier->bActionPoints <= 0 ) //Action points can be negative { pSoldier->aiData.usActionData = NOWHERE; return(AI_ACTION_NONE); @@ -2163,7 +2163,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) } } - if(WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909 + //Only put mask on in gas + if(bInGas && WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909 bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo); //////////////////////////////////////////////////////////////////////////// @@ -2464,22 +2465,46 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) return(AI_ACTION_NONE); } } - } + } + //RELOADING + + // WarmSteel - Because of suppression fire, we need enough ammo to even consider suppressing + // This means we need to reload. Also reload if we're just plainly low on bullets. + if(BestShot.bWeaponIn != NO_SLOT + && ((pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < gGameExternalOptions.ubAISuppressionMinimumAmmo && GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize) + || pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft < (UINT8)(GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) / 4) )) + { + // HEADROCK HAM 5: Fixed an issue where no ammo was found, leading to a crash when overloading the + // inventory vector (bAmmoSlot = -1...) + INT8 bAmmoSlot = FindAmmoToReload( pSoldier, BestShot.bWeaponIn, NO_SLOT ); + if (bAmmoSlot > -1) + { + OBJECTTYPE * pAmmo = &(pSoldier->inv[bAmmoSlot]); + if((*pAmmo)[0]->data.ubShotsLeft > pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft && GetAPsToReloadGunWithAmmo( pSoldier, &(pSoldier->inv[BestShot.bWeaponIn]), pAmmo ) <= (INT16)pSoldier->bActionPoints) + { + pSoldier->aiData.usActionData = BestShot.bWeaponIn; + return AI_ACTION_RELOAD_GUN; + } + } + } //SUPPRESSION FIRE - CheckIfShotPossible(pSoldier,&BestShot,TRUE); + CheckIfShotPossible(pSoldier,&BestShot,TRUE); //WarmSteel - No longer returns 0 when there IS actually a chance to hit. //must have a small chance to hit and the opponent must be on the ground (can't suppress guys on the roof) // HEADROCK HAM BETA2.4: Adjusted this for a random chance to suppress regardless of chance. This augments // current revamp of suppression fire. - BOOLEAN fEnableAIAutofire = FALSE; // CHRISL: Changed from a simple flag to two externalized values for more modder control over AI suppression - if ( BestShot.bWeaponIn != -1 && (GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize && pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo) && ( ( BestShot.ubPossible && BestShot.ubChanceToReallyHit < 50 ) || (BestShot.ubPossible && BestShot.ubChanceToReallyHit < (INT16)PreRandom(100)) && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 && pSoldier->aiData.bOrders != SNIPER )) - fEnableAIAutofire = TRUE; - - if (fEnableAIAutofire) + // WarmSteel - Don't *always* try to suppress when under 50 CTH + if ( BestShot.bWeaponIn != -1 + && BestShot.ubPossible + && GetMagSize(&pSoldier->inv[BestShot.bWeaponIn]) >= gGameExternalOptions.ubAISuppressionMinimumMagSize + && pSoldier->inv[BestShot.bWeaponIn][0]->data.gun.ubGunShotsLeft >= gGameExternalOptions.ubAISuppressionMinimumAmmo + && BestShot.ubChanceToReallyHit < (INT16)(PreRandom(100) - 50) + && Menptr[BestShot.ubOpponent].pathing.bLevel == 0 + && pSoldier->aiData.bOrders != SNIPER ) { // then do it! @@ -2522,9 +2547,9 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) // Make sure we decided to fire at least one shot! ubBurstAPs = CalcAPsToAutofire( pSoldier->CalcActionPoints(), &(pSoldier->inv[BestShot.bWeaponIn]), pSoldier->bDoAutofire ); - // minimum 10 bullets + // minimum 5 bullets //WarmSteel: 5 bullets sounds reasonable, no? // Hmmm, automatic suppression? Howcome we don't get this? - if (pSoldier->bDoAutofire >= 10 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs ) + if (pSoldier->bDoAutofire >= 5 && pSoldier->bActionPoints >= BestShot.ubAPCost + ubBurstAPs ) { pSoldier->aiData.bAimTime = 0; pSoldier->bDoBurst = 1; @@ -3203,8 +3228,8 @@ INT8 DecideActionRed(SOLDIERTYPE *pSoldier, UINT8 ubUnconsciousOK) guiRedHelpTimeTotal += (uiEndTime - uiStartTime); guiRedHelpCounter++; #endif - - if (!TileIsOutOfBounds(sClosestFriend)) + //WarmSteel - Dont try if we're already quite close to our friend + if (!TileIsOutOfBounds(sClosestFriend) && PythSpacesAway(pSoldier->sGridNo, sClosestFriend) > pSoldier->GetMaxDistanceVisible(sClosestFriend, 0, CALC_FROM_ALL_DIRS )) { ////////////////////////////////////////////////////////////////////// // GO DIRECTLY TOWARDS CLOSEST FRIEND UNDER FIRE OR WHO LAST RADIOED @@ -3900,8 +3925,9 @@ INT16 ubMinAPCost; } } - if(WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909 - bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo); + //Only put mask on in gas + if(bInGas && WearGasMaskIfAvailable(pSoldier))//dnl ch40 200909 + bInGas = InGasOrSmoke(pSoldier, pSoldier->sGridNo); //////////////////////////////////////////////////////////////////////////// // IF GASSED, OR REALLY TIRED (ON THE VERGE OF COLLAPSING), TRY TO RUN AWAY diff --git a/TacticalAI/FindLocations.cpp b/TacticalAI/FindLocations.cpp index c6914b6e..d3cdbe80 100644 --- a/TacticalAI/FindLocations.cpp +++ b/TacticalAI/FindLocations.cpp @@ -651,7 +651,8 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB // HEADROCK HAM 3.6: This doesn't take into account the 100AP system. Adjusting. // Please note, I used a calculation that may have a better representation in some global variable. //iMaxMoveTilesLeft = __max( 0, pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) ); - iMaxMoveTilesLeft = __max( 0, (pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode ) / (APBPConstants[AP_MAXIMUM]/25)) ); + // WarmSteel - Bugfix: wrong parentheses + iMaxMoveTilesLeft = __max( 0, (pSoldier->bActionPoints - MinAPsToStartMovement( pSoldier, usMovementMode )) / (APBPConstants[AP_MAXIMUM] / 25) ); //NumMessage("In BLACK, maximum tiles to move left = ",maxMoveTilesLeft); @@ -799,7 +800,17 @@ INT32 FindBestNearbyCover(SOLDIERTYPE *pSoldier, INT32 morale, INT32 *piPercentB //PopMessage(tempstr); } - iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + // reduce cover for each person adjacent to this gridno who is on our team, + // by 10% (so locations next to several people will be very much frowned upon + if ( iCurrentCoverValue >= 0 ) + { + iCurrentCoverValue -= (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + } + else + { + // when negative, must add a negative to decrease the total + iCurrentCoverValue += (iCurrentCoverValue / 10) * NumberOfTeamMatesAdjacent( pSoldier, pSoldier->sGridNo ); + } #ifdef DEBUGCOVER // AINumMessage("Search Range = ",iSearchRange); diff --git a/TacticalAI/ai.h b/TacticalAI/ai.h index f82d04b4..bd49db93 100644 --- a/TacticalAI/ai.h +++ b/TacticalAI/ai.h @@ -98,6 +98,8 @@ typedef enum AI_ACTION_OFFER_SURRENDER, // offer surrender to the player AI_ACTION_RAISE_GUN, AI_ACTION_STEAL_MOVE, // added by SANDRO + + AI_ACTION_RELOAD_GUN, } ActionType; diff --git a/TileEngine/Explosion Control.cpp b/TileEngine/Explosion Control.cpp index 42315c4a..39816991 100644 --- a/TileEngine/Explosion Control.cpp +++ b/TileEngine/Explosion Control.cpp @@ -103,6 +103,9 @@ BOOLEAN ExpAffect( INT32 sBombGridNo, INT32 sGridNo, UINT32 uiDist, UINT16 usIte // Flashbang effect on soldier UINT8 DetermineFlashbangEffect( SOLDIERTYPE *pSoldier, INT8 ubExplosionDir, BOOLEAN fInBuilding); +// HEADROCK HAM 5.1: Explosion Fragments launcher +void FireFragments( SOLDIERTYPE * pThrower, INT16 sX, INT16 sY, INT16 sZ, UINT16 usItem ); + extern INT8 gbSAMGraphicList[ MAX_NUMBER_OF_SAMS ]; extern void AddToShouldBecomeHostileOrSayQuoteList( UINT8 ubID ); extern void RecompileLocalMovementCostsForWall( INT32 sGridNo, UINT8 ubOrientation ); @@ -338,12 +341,17 @@ void InternalIgniteExplosion( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 ExpParams.bLevel = bLevel; GenerateExplosion( &ExpParams ); + + // HEADROCK HAM 5.1: Launch fragments from the explosion. + if (Explosive[ Item[ usItem ].ubClassIndex ].usNumFragments > 0 ) + { + // HEADROCK HAM 5: Deactivated until the release of HAM 5.1. + FireFragments( MercPtrs[ubOwner], sX, sY, sZ, usItem ); + } } - - void IgniteExplosion( UINT8 ubOwner, INT16 sX, INT16 sY, INT16 sZ, INT32 sGridNo, UINT16 usItem, INT8 bLevel ) { InternalIgniteExplosion( ubOwner, sX, sY, sZ, sGridNo, usItem, TRUE, bLevel ); @@ -4349,6 +4357,43 @@ UINT8 DetermineFlashbangEffect( SOLDIERTYPE *pSoldier, INT8 ubExplosionDir, BOOL return ( FIRE_WEAPON_BLINDED_AND_DEAFENED ); } + +// HEADROCK HAM 5.1: This handles launching fragments out of an explosion. The number of fragments is read from +// the Explosives.XML file, and they each have a set amount of damage and range as well. They are currently +// fired at completely random trajectories. +void FireFragments( SOLDIERTYPE * pThrower, INT16 sX, INT16 sY, INT16 sZ, UINT16 usItem ) +{ + UINT16 usNumFragments = Explosive[Item[usItem].ubClassIndex].usNumFragments; + UINT16 ubFragRange = Explosive[Item[usItem].ubClassIndex].ubFragRange; + + AssertMsg( ubFragRange > 0 , "Fragmentation data lacks range property!" ); + + for (UINT16 x = 0; x < usNumFragments; x++) + { + FLOAT dRandomX = ((FLOAT)Random(2000) / 1000.0f) - 1.0f; + FLOAT dRandomY = ((FLOAT)Random(2000) / 1000.0f) - 1.0f; + FLOAT dRandomZ = ((FLOAT)Random(2000) / 1000.0f) - 1.0f; + + FLOAT dDeltaX = (dRandomX * ubFragRange); + FLOAT dDeltaY = (dRandomY * ubFragRange); + FLOAT dDeltaZ = ((dRandomZ * 25.6f) * 50 ); + + FLOAT dRangeMultiplier = 10; // Arbitrary, but gives good results. + + FLOAT dEndX = (FLOAT)(sX + (dDeltaX * dRangeMultiplier)); + FLOAT dEndY = (FLOAT)(sY + (dDeltaY * dRangeMultiplier)); + FLOAT dEndZ = (FLOAT)(sZ + (dDeltaZ * dRangeMultiplier)); + + // Add some randomness to the start coordinates as well, so that not all fragments fly from the same point + // in space. + FLOAT dStartX = (FLOAT)sX + (dRandomX * ((FLOAT)Random(4)+1.0f)); + FLOAT dStartY = (FLOAT)sY + (dRandomY * ((FLOAT)Random(4)+1.0f)); + FLOAT dStartZ = (FLOAT)sZ + (dRandomZ * ((FLOAT)Random(4)+1.0f)); + + FireFragmentGivenTarget( pThrower, dStartX, dStartY, dStartZ, dEndX, dEndY, dEndZ, usItem ); + } +} + #ifdef JA2UB //-- UB diff --git a/TileEngine/SmokeEffects.cpp b/TileEngine/SmokeEffects.cpp index f0d7043b..f2cd8ec6 100644 --- a/TileEngine/SmokeEffects.cpp +++ b/TileEngine/SmokeEffects.cpp @@ -546,45 +546,43 @@ void DecaySmokeEffects( UINT32 uiTime ) if ( pSmoke->fAllocated ) { - if ( pSmoke->bFlags & SMOKE_EFFECT_ON_ROOF ) - { - bLevel = 1; - } - else - { - bLevel = 0; - } - - - // Do things differently for combat /vs realtime - // always try to update during combat - if (gTacticalStatus.uiFlags & INCOMBAT ) - { - fUpdate = TRUE; - } - else - { - // ATE: Do this every so ofte, to acheive the effect we want... - if ( ( uiTime - pSmoke->uiTimeOfLastUpdate ) > 10 ) + if ( pSmoke->bFlags & SMOKE_EFFECT_ON_ROOF ) { - fUpdate = TRUE; + bLevel = 1; + } + else + { + bLevel = 0; + } - usNumUpdates = ( UINT16 ) ( ( uiTime - pSmoke->uiTimeOfLastUpdate ) / 10 ); - } - } + // Do things differently for combat /vs realtime + // always try to update during combat + if (gTacticalStatus.uiFlags & INCOMBAT ) + { + fUpdate = TRUE; + } + else + { + // ATE: Do this every so ofte, to acheive the effect we want... + if ( ( uiTime - pSmoke->uiTimeOfLastUpdate ) > 10 ) + { + fUpdate = TRUE; + usNumUpdates = ( UINT16 ) ( ( uiTime - pSmoke->uiTimeOfLastUpdate ) / 10 ); + } + } - if ( fUpdate ) - { + if ( fUpdate ) + { pSmoke->uiTimeOfLastUpdate = uiTime; - for ( cnt2 = 0; cnt2 < usNumUpdates; cnt2++ ) - { - pSmoke->bAge++; - + for ( cnt2 = 0; cnt2 < usNumUpdates; cnt2++ ) + { + pSmoke->bAge++; + if ( pSmoke->bAge == 1 ) { - // ATE: At least mark for update! - pSmoke->bFlags |= SMOKE_EFFECT_MARK_FOR_UPDATE; + // ATE: At least mark for update! + pSmoke->bFlags |= SMOKE_EFFECT_MARK_FOR_UPDATE; fSpreadEffect = FALSE; } else @@ -592,41 +590,41 @@ void DecaySmokeEffects( UINT32 uiTime ) fSpreadEffect = TRUE; } - if ( fSpreadEffect ) - { - // if this cloud remains effective (duration not reached) - if ( pSmoke->bAge <= pSmoke->ubDuration) - { - // ATE: Only mark now and increse radius - actual drawing is done - // in another pass cause it could - // just get erased... - pSmoke->bFlags |= SMOKE_EFFECT_MARK_FOR_UPDATE; + if ( fSpreadEffect ) + { + // if this cloud remains effective (duration not reached) + if ( pSmoke->bAge <= pSmoke->ubDuration) + { + // ATE: Only mark now and increse radius - actual drawing is done + // in another pass cause it could + // just get erased... + pSmoke->bFlags |= SMOKE_EFFECT_MARK_FOR_UPDATE; - // calculate the new cloud radius - // cloud expands by 1 every turn outdoors, and every other turn indoors + // calculate the new cloud radius + // cloud expands by 1 every turn outdoors, and every other turn indoors - // ATE: If radius is < maximun, increase radius, otherwise keep at max - if ( pSmoke->ubRadius < Explosive[ Item[ pSmoke->usItem ].ubClassIndex ].ubRadius ) - { - pSmoke->ubRadius++; + // ATE: If radius is < maximun, increase radius, otherwise keep at max + if ( pSmoke->ubRadius < Explosive[ Item[ pSmoke->usItem ].ubClassIndex ].ubRadius ) + { + pSmoke->ubRadius++; + } + } + else + { + // deactivate tear gas cloud (use last known radius) + SpreadEffect( pSmoke->sGridNo, pSmoke->ubRadius, pSmoke->usItem, pSmoke->ubOwner, ERASE_SPREAD_EFFECT, bLevel, cnt ); + pSmoke->fAllocated = FALSE; + break; + } + } } - } - else - { - // deactivate tear gas cloud (use last known radius) - SpreadEffect( pSmoke->sGridNo, pSmoke->ubRadius, pSmoke->usItem, pSmoke->ubOwner, ERASE_SPREAD_EFFECT, bLevel, cnt ); - pSmoke->fAllocated = FALSE; - break; - } - } - } } else { // damage anyone standing in cloud SpreadEffect( pSmoke->sGridNo, pSmoke->ubRadius, pSmoke->usItem, pSmoke->ubOwner, REDO_SPREAD_EFFECT, bLevel, cnt ); } - } + } } for ( cnt = 0; cnt < guiNumSmokeEffects; cnt++ ) @@ -635,22 +633,22 @@ void DecaySmokeEffects( UINT32 uiTime ) if ( pSmoke->fAllocated ) { - if ( pSmoke->bFlags & SMOKE_EFFECT_ON_ROOF ) - { - bLevel = 1; - } - else - { - bLevel = 0; - } + if ( pSmoke->bFlags & SMOKE_EFFECT_ON_ROOF ) + { + bLevel = 1; + } + else + { + bLevel = 0; + } - // if this cloud remains effective (duration not reached) - if ( pSmoke->bFlags & SMOKE_EFFECT_MARK_FOR_UPDATE ) + // if this cloud remains effective (duration not reached) + if ( pSmoke->bFlags & SMOKE_EFFECT_MARK_FOR_UPDATE ) { SpreadEffect( pSmoke->sGridNo, pSmoke->ubRadius, pSmoke->usItem, pSmoke->ubOwner, TRUE, bLevel, cnt ); - pSmoke->bFlags &= (~SMOKE_EFFECT_MARK_FOR_UPDATE); + pSmoke->bFlags &= (~SMOKE_EFFECT_MARK_FOR_UPDATE); } - } + } } AllTeamsLookForAll( TRUE ); diff --git a/TileEngine/SmokeEffects.h b/TileEngine/SmokeEffects.h index e73efd31..32f5643d 100644 --- a/TileEngine/SmokeEffects.h +++ b/TileEngine/SmokeEffects.h @@ -44,6 +44,8 @@ extern UINT32 guiNumSmokeEffects; INT8 GetSmokeEffectOnTile( INT32 sGridNo, INT8 bLevel ); // Decays all smoke effects... +// HEADROCK HAM 5: New argument here tells the decay function to only process clouds belonging to one team. +// -1 = all teams. void DecaySmokeEffects( UINT32 uiTime ); // Add smoke to gridno diff --git a/TileEngine/environment.cpp b/TileEngine/environment.cpp index f32f0706..d969f60b 100644 --- a/TileEngine/environment.cpp +++ b/TileEngine/environment.cpp @@ -220,7 +220,14 @@ void EnvironmentController( BOOLEAN fCheckForLights ) { if ( guiRainLoop == NO_SAMPLE ) { - guiRainLoop = PlayJA2Ambient( RAIN_1, BTNVOLUME, 0 ); + if (guiEnvWeather & WEATHER_FORECAST_THUNDERSHOWERS ) + { + guiRainLoop = PlayJA2Ambient( RAIN_1, 140, 0 ); + } + else + { + guiRainLoop = PlayJA2Ambient( RAIN_1, 70, 0 ); + } } } @@ -707,24 +714,26 @@ void EnvBeginRainStorm( UINT8 ubIntensity ) { gfDoLighting = TRUE; - #ifdef JA2TESTVERSION - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Starting Rain...." ); - #endif + #ifdef JA2TESTVERSION + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_TESTVERSION, L"Starting Rain...." ); + #endif - // First turn off whatever rain it is, then turn on the requested rain - guiEnvWeather &= ~(WEATHER_FORECAST_THUNDERSHOWERS | WEATHER_FORECAST_SHOWERS); + // First turn off whatever rain it is, then turn on the requested rain + guiEnvWeather &= ~(WEATHER_FORECAST_THUNDERSHOWERS | WEATHER_FORECAST_SHOWERS); - if ( ubIntensity == 1 ) - { - // Turn on rain storms - guiEnvWeather |= WEATHER_FORECAST_THUNDERSHOWERS; - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_STORM_STARTED] ); - } - else - { - guiEnvWeather |= WEATHER_FORECAST_SHOWERS; - ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_RAIN_STARTED] ); - } + if ( ubIntensity == 1 ) + { + // Turn on rain storms + guiEnvWeather |= WEATHER_FORECAST_THUNDERSHOWERS; + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_STORM_STARTED] ); + } + else + { + guiEnvWeather |= WEATHER_FORECAST_SHOWERS; + + ScreenMsg( FONT_MCOLOR_LTYELLOW, MSG_INTERFACE, New113Message[MSG113_RAIN_STARTED] ); + } } } diff --git a/TileEngine/physics.cpp b/TileEngine/physics.cpp index a032d36f..3fd215e4 100644 --- a/TileEngine/physics.cpp +++ b/TileEngine/physics.cpp @@ -62,7 +62,8 @@ class SOLDIERTYPE; #define GET_OBJECT_LEVEL( z ) ( (INT8)( ( z + 10 ) / HEIGHT_UNITS ) ) //#define OBJECT_DETONATE_ON_IMPACT( object ) ( ( object->Obj.usItem == MORTAR_SHELL ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) ) -#define OBJECT_DETONATE_ON_IMPACT( object ) ( ( Item[object->Obj.usItem].usItemClass == IC_BOMB ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) ) +// HEADROCK HAM 5: Enabled "Explode on Impact" flag for explosive items +#define OBJECT_DETONATE_ON_IMPACT( object ) ( ( Item[object->Obj.usItem].usItemClass == IC_BOMB ) || ( Explosive[Item[object->Obj.usItem].ubClassIndex ].fExplodeOnImpact ) ) // && ( object->ubActionCode == THROW_ARM_ITEM || pObject->fTestObject ) ) #define MAX_INTEGRATIONS 8 diff --git a/Utils/ExportStrings.cpp b/Utils/ExportStrings.cpp index f70ef843..a138f1b3 100644 --- a/Utils/ExportStrings.cpp +++ b/Utils/ExportStrings.cpp @@ -345,7 +345,7 @@ bool Loc::ExportStrings() ExportSection(props, L"TooltipStrings", Loc::gzTooltipStrings, 0, TEXT_NUM_STR_TT); ExportSection(props, L"New113Message", Loc::New113Message, 0, TEXT_NUM_MSG113); - ExportSection(props, L"New113HAMMessage", Loc::New113HAMMessage, 0, 22); + ExportSection(props, L"New113HAMMessage", Loc::New113HAMMessage, 0, 25); ExportSection(props, L"New113MERCMercMail", Loc::New113MERCMercMailTexts, 0, 4); ExportSection(props, L"New113AIMMercMail", Loc::New113AIMMercMailTexts, 0, 16); ExportSection(props, L"MissingIMPSkills", Loc::MissingIMPSkillsDescriptions, 0, 2); diff --git a/Utils/PopUpBox.h b/Utils/PopUpBox.h index 08ee41ba..033e6227 100644 --- a/Utils/PopUpBox.h +++ b/Utils/PopUpBox.h @@ -7,8 +7,8 @@ #include "WCheck.h" #include "Render Dirty.h" -#define MAX_POPUP_BOX_COUNT 20 -#define MAX_POPUP_BOX_STRING_COUNT 50 // worst case = 45: move menu with 20 soldiers, each on different squad + overhead +#define MAX_POPUP_BOX_COUNT 32 +#define MAX_POPUP_BOX_STRING_COUNT 128 // worst case = 45: move menu with 20 soldiers, each on different squad + overhead // PopUpBox Flags #define POPUP_BOX_FLAG_CLIP_TEXT 1 diff --git a/Utils/Text.h b/Utils/Text.h index 1acb4fe3..dea8dbbc 100644 --- a/Utils/Text.h +++ b/Utils/Text.h @@ -322,6 +322,10 @@ extern STR16 gzFacilityRiskResultStrings[]; // HEADROCK HAM 4: Text for the new CTH indicator. extern STR16 gzNCTHlabels[]; +// HEADROCK HAM 5: Messages for automatic sector inventory sorting. +extern STR16 gzMapInventorySortingMessage[]; +extern STR16 gzMapInventoryFilterOptions[]; + enum { ANTIHACKERSTR_EXITGAME, @@ -624,14 +628,14 @@ extern STR16 gzUDBButtonTooltipText[ 3 ]; extern STR16 gzUDBHeaderTooltipText[ 4 ]; extern STR16 gzUDBGenIndexTooltipText[ 4 ]; extern STR16 gzUDBAdvIndexTooltipText[ 5 ]; -extern STR16 szUDBGenWeaponsStatsTooltipText[ 22 ]; -extern STR16 szUDBGenWeaponsStatsExplanationsTooltipText[ 22 ]; +extern STR16 szUDBGenWeaponsStatsTooltipText[ 23 ]; +extern STR16 szUDBGenWeaponsStatsExplanationsTooltipText[ 23 ]; extern STR16 szUDBGenArmorStatsTooltipText[ 3 ]; extern STR16 szUDBGenArmorStatsExplanationsTooltipText[ 3 ]; extern STR16 szUDBGenAmmoStatsTooltipText[ 4 ]; // Flugente Overheating Weapons: 3->4 extern STR16 szUDBGenAmmoStatsExplanationsTooltipText[ 4 ]; -extern STR16 szUDBGenExplosiveStatsTooltipText[ 18 ]; -extern STR16 szUDBGenExplosiveStatsExplanationsTooltipText[ 18 ]; +extern STR16 szUDBGenExplosiveStatsTooltipText[ 22 ]; +extern STR16 szUDBGenExplosiveStatsExplanationsTooltipText[ 22 ]; extern STR16 szUDBGenSecondaryStatsTooltipText[ 26 ]; extern STR16 szUDBGenSecondaryStatsExplanationsTooltipText[ 26 ]; extern STR16 szUDBAdvStatsTooltipText[ 56 ]; // Flugente Overheating Weapons: 48->56 @@ -2101,6 +2105,8 @@ enum TEXT_NUM_MSG113, }; +extern STR16 gzTransformationMessage[]; + //CHRISL: NewInv messages extern STR16 NewInvMessage[]; diff --git a/Utils/Utils_VS2010.vcxproj b/Utils/Utils_VS2010.vcxproj index 93bbabeb..b494c284 100644 --- a/Utils/Utils_VS2010.vcxproj +++ b/Utils/Utils_VS2010.vcxproj @@ -48,6 +48,7 @@ + @@ -98,6 +99,7 @@ + diff --git a/Utils/Utils_VS2010.vcxproj.filters b/Utils/Utils_VS2010.vcxproj.filters index 6b74e20b..3d0cea89 100644 --- a/Utils/Utils_VS2010.vcxproj.filters +++ b/Utils/Utils_VS2010.vcxproj.filters @@ -159,6 +159,9 @@ Header Files + + Header Files + @@ -338,5 +341,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/Utils/_EnglishText.cpp b/Utils/_EnglishText.cpp index 7190d6a8..80e0f660 100644 --- a/Utils/_EnglishText.cpp +++ b/Utils/_EnglishText.cpp @@ -870,7 +870,7 @@ STR16 pPasteMercPlacementText[] = { L"Placement not pasted as no placement is saved in buffer.", L"Placement pasted.", - L"Placement not pasted as the maximum number of placements for this team is reached.", + L"Placement not pasted as the maximum number of placements for this team has been reached.", }; //editscreen.cpp @@ -2471,11 +2471,13 @@ CHAR16 gWeaponStatsDesc[][ 17 ] = // HEADROCK: Several arrays of tooltip text for new Extended Description Box // Please note, several of these are artificially inflated to 19 entries to help fix a complication with // changing item type while watching its description box +// HEADROCK HAM 5: Apparently this isn't used anymore... see the next array. STR16 gzWeaponStatsFasthelp[ 32 ] = { L"Accuracy", L"Damage", L"Range", + L"", //3 L"Aiming Levels", L"Aiming Modifier", L"Average Best Laser Range", @@ -2485,7 +2487,6 @@ STR16 gzWeaponStatsFasthelp[ 32 ] = L"Repair Ease", L"Min. Range for Aiming Bonus", L"To-Hit Modifier", - L"", //12 L"APs to ready", L"APs to fire Single", L"APs to fire Burst", @@ -2509,18 +2510,18 @@ STR16 gzWeaponStatsFasthelp[ 32 ] = STR16 gzWeaponStatsFasthelpTactical[ 32 ] = { - L"Accuracy", - L"Damage", - L"Range", - L"Aiming Levels", - L"Aiming Modifier", - L"Average Best Laser Range", - L"Flash Suppression", - L"Loudness (lower is better)", - L"Reliability", - L"Repair Ease", - L"Min. Range for Aiming Bonus", - L"To-Hit Modifier", + L"|R|a|n|g|e\n \nThe effective range of this weapon. Attacking from\nbeyond this range will lead to massive penalties.\n \nHigher is better.", + L"|D|a|m|a|g|e\n \nThis is the damage potential of the weapon.\nIt will usually deliver this much damage\n(or close to it) to any unprotected target.\n \nHigher is better.", + L"|A|c|c|u|r|a|c|y\n \nThis is an innate Chance-to-Hit Bonus (or\npenalty!) given by this gun due to its\nparticular good (or bad) design.\n \nHigher is better.", + L"|A|i|m|i|n|g |L|e|v|e|l|s\n \nThis is the maximum number of aiming clicks allowed\nwhen using this gun.\n \nEach aiming-click will make an attack more\naccurate.\n \nHigher is better.", + L"|A|i|m|i|n|g |M|o|d|i|f|i|e|r\n \nA flat modifier, which alters the effectiveness\nof each aiming click you make while using this\nweapon.\n \nHigher is better.", + L"|M|i|n|. |R|a|n|g|e |f|o|r |A|i|m|i|n|g |B|o|n|u|s\n \nThe minimum range-to-target required before this\nweapon can make use of its Aiming Modifier.\n \nIf the target is closer than this many tiles,\naiming clicks will stay at their default\neffectiveness.\n \nLower is better.", + L"|T|o|-|H|i|t |M|o|d|i|f|i|e|r\n \nA flat modifier to Chance-to-Hit with any\nattack made using this weapon.\n \nHigher is better.", + L"|B|e|s|t |L|a|s|e|r |R|a|n|g|e\n \nThe range (in tiles) at which the laser installed\non this weapon will be at its full effectiveness.\n \nWhen attacking a target beyond this range, the\nlaser will provide a smaller bonus or none at all.\n \nHigher is better.", + L"|F|l|a|s|h |S|u|p|p|r|e|s|s|i|o|n\n \nWhen this icon appears, it means that the gun\ndoes not make a flash when it fires. This helps the\nshooter remain concealed.", + L"|L|o|u|d|n|e|s|s\n \nAttacks made with this weapon can be heard up to\nthe listed distance (in tiles).\n \nLower is better.\n(unless deliberately trying to draw in enemies...)", + L"|R|e|l|i|a|b|i|l|i|t|y\n \nThis value indicates (in general) how quickly\nthis weapon will degrade when used in combat.\n \nHigher is better.", + L"|R|e|p|a|i|r |E|a|s|e\n \nThis value indicates how quickly this weapon can\nbe repaired (by a mercenary set to Repair duty).\n \nHigher is better.", L"", //12 L"APs to ready", L"APs to fire Single", @@ -3188,6 +3189,23 @@ STR16 pMapScreenInvenButtonHelpText[] = L"Next (|.)", // next page L"Previous (|,)", // previous page L"Exit Sector Inventory (|E|s|c)", // exit sector inventory + L"Zoom Inventory", // HEAROCK HAM 5: Inventory Zoom Button + L"Stack and merge items", // HEADROCK HAM 5: Stack and Merge + L"|L|e|f|t |C|l|i|c|k: Sort ammo into crates; |R|i|g|h|t |C|l|i|c|k: Sort ammo into boxes", // HEADROCK HAM 5: Sort ammo + // 6 - 10 + L"Remove all item attachments", // HEADROCK HAM 5: Separate Attachments + L"Eject ammo from all weapons", //HEADROCK HAM 5: Eject Ammo + L"|L|e|f|t |C|l|i|c|k: Show all items; |R|i|g|h|t |C|l|i|c|k: Hide all items", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle Guns; |R|i|g|h|t |C|l|i|c|k|: Show only Guns", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle Ammunition; |R|i|g|h|t |C|l|i|c|k: Show only Ammunition", // HEADROCK HAM 5: Filter Button + // 11 - 15 + L"|L|e|f|t |C|l|i|c|k: Toggle Explosives; |R|i|g|h|t |C|l|i|c|k: Show only Explosives", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle Melee Weapons; |R|i|g|h|t |C|l|i|c|k: Show only Melee Weapons", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle Armor; |R|i|g|h|t |C|l|i|c|k: Show only Armor", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle LBEs; |R|i|g|h|t |C|l|i|c|k: Show only LBEs", // HEADROCK HAM 5: Filter Button + L"|L|e|f|t |C|l|i|c|k: Toggle Kits; |R|i|g|h|t |C|l|i|c|k: Show only Kits", // HEADROCK HAM 5: Filter Button + // 16 - 20 + L"|L|e|f|t |C|l|i|c|k: Toggle Misc. Items; |R|i|g|h|t |C|l|i|c|k: Show only Misc. Items", // HEADROCK HAM 5: Filter Button }; STR16 pMapScreenBottomFastHelp[] = @@ -6121,7 +6139,26 @@ STR16 New113HAMMessage[] = L"Daily Expenses", // 21 - 25 L"Insufficient funds to pay all enlisted militia! %d militia have disbanded and returned home.", + L"To examine an item's stats during combat, you must pick it up manually first.", // HAM 5 + L"To attach an item to another item during combat, you must pick them both up first.", // HAM 5 + L"To merge two items during combat, you must pick them both up first.", // HAM 5 +}; +// HEADROCK HAM 5: Text dealing exclusively with Item Transformations. +STR16 gzTransformationMessage[] = +{ + L"No available adjustments", + L"%s was split into several parts.", + L"%s was split into several parts. Check %s's inventory for the resulting items.", + L"Due to lack of inventory space after transformation, some of %s's items have been dropped to the ground.", + L"%s was split into several parts. Due to a lack of inventory space, %s has had to drop a few items to the ground.", + L"Do you wish to adjust all %d items in this stack? (To transform only one item, remove it from the stack first)", + // 6 - 10 + L"Split Crate into Inventory", + L"Split into %d-rd Mags", + L"%s was split into %d Magazines containing %d rounds each.", + L"%s was split into %s's inventory.", + L"There is insufficient room in %s's inventory to fit any magazines of this caliber!", }; // WANNE: This hardcoded text should not be used anymore in the game, because we now have those texts externalized in the "TableData\Email\EmailMercLevelUp.xml" file! @@ -6790,6 +6827,7 @@ STR16 szUDBGenWeaponsStatsTooltipText[]= L"|A|c|c|u|r|a|c|y", L"|D|a|m|a|g|e", L"|R|a|n|g|e", + L"|H|a|n|d|l|i|n|g |D|i|f|f|i|c|u|l|t|y", L"|A|l|l|o|w|e|d |A|i|m|i|n|g |L|e|v|e|l|s", L"|S|c|o|p|e |M|a|g|n|i|f|i|c|a|t|i|o|n |F|a|c|t|o|r", L"|P|r|o|j|e|c|t|i|o|n |F|a|c|t|o|r", @@ -6799,15 +6837,14 @@ STR16 szUDBGenWeaponsStatsTooltipText[]= L"|R|e|p|a|i|r |E|a|s|e", L"|M|i|n|. |R|a|n|g|e |f|o|r |A|i|m|i|n|g |B|o|n|u|s", L"|T|o|-|H|i|t |M|o|d|i|f|i|e|r", - L"", // (12) L"|A|P|s |t|o |R|e|a|d|y", L"|A|P|s |t|o |A|t|t|a|c|k", L"|A|P|s |t|o |B|u|r|s|t", L"|A|P|s |t|o |A|u|t|o|f|i|r|e", L"|A|P|s |t|o |R|e|l|o|a|d", L"|A|P|s |t|o |R|e|c|h|a|m|b|e|r", - L"|L|a|t|e|r|a|l |R|e|c|o|i|l", - L"|V|e|r|t|i|c|a|l |R|e|c|o|i|l", + L"|L|a|t|e|r|a|l |R|e|c|o|i|l", // No longer used + L"|T|o|t|a|l |R|e|c|o|i|l", L"|A|u|t|o|f|i|r|e |B|u|l|l|e|t|s |p|e|r |5 |A|P|s", }; @@ -6816,6 +6853,7 @@ STR16 szUDBGenWeaponsStatsExplanationsTooltipText[]= L"\n \nDetermines whether bullets fired by\nthis gun will stray far from where\nit is pointed.\n \nScale: 0-100.\nHigher is better.", L"\n \nDetermines the average amount of damage done\nby bullets fired from this weapon, before\ntaking into account armor or armor-penetration.\n \nHigher is better.", L"\n \nThe maximum distance (in tiles) that\nbullets fired from this gun will travel\nbefore they begin dropping towards the\nground.\n \nHigher is better.", + L"\n \nDetermines the difficulty of holding and firing\nthis gun.\n \nHigher Handling Difficulty results in lower\nchance-to-hit - when aimed and especially when\nunaimed.\n \nLower is better.", L"\n \nThis is the number of Extra Aiming\nLevels you can add when aiming this gun.\n \nThe FEWER aiming levels are allowed, the MORE\nbonus each aiming level gives you. Therefore,\nhaving FEWER levels makes the gun faster to aim,\nwithout making it any less accurate.\n \nLower is better.", L"\n \nWhen greater than 1.0, will proportionally reduce\naiming errors at a distance.\n \nRemember that high scope magnification is detrimental\nwhen the target is too close!\n \nA value of 1.0 means no scope is installed.", L"\n \nProportionally reduces aiming errors at a distance.\n \nThis effect works up to a given distance,\nthen begins to dissipate and eventually\ndisappears at sufficient range.\n \nHigher is better.", @@ -6825,15 +6863,14 @@ STR16 szUDBGenWeaponsStatsExplanationsTooltipText[]= L"\n \nDetermines how difficult it is to repair this weapon.\n \nHigher is better.", L"\n \nThe minimum range at which a scope can provide it's aimBonus.", L"\n \nTo hit modifier granted by laser sights.", - L"", // (12) L"\n \nThe number of APs required to bring this\nweapon up to firing stance.\n \nOnce the weapon is raised, you may fire repeatedly\nwithout paying this cost again.\n \nA weapon is automatically 'Unreadied' if its\nwielder performs any action other than\nfiring or turning.\n \nLower is better.", L"\n \nThe number of APs required to perform\na single attack with this weapon.\n \nFor guns, this is the cost of firing\na single shot without extra aiming.\n \nIf this icon is greyed-out, single-shots\n are not possible with this weapon.\n \nLower is better.", L"\n \nThe number of APs required to fire\na burst.\n \nThe number of bullets fired in each burst is\ndetermined by the weapon itself, and indicated\nby the number of bullets shown on this icon.\n \nIf this icon is greyed-out, burst fire\nis not possible with this weapon.\n \nLower is better.", L"\n \nThe number of APs required to fire\nan Autofire Volley of three bullets.\n \nIf you wish to fire more than 3 bullets,\nyou will need to pay extra APs.\n \nIf this icon is greyed-out, autofire\nis not possible with this weapon.\n \nLower is better.", L"\n \nThe number of APs required to reload\nthis weapon.\n \nLower is better.", L"\n \nThe number of APs required to rechamber this weapon\nbetween each and every shot fired.\n \nLower is better.", - L"\n \nThe distance this weapon's muzzle will shift\nhorizontally between each and every bullet in a\nburst or autofire volley.\n \nPositive numbers indicate shifting to the right.\nNegative numbers indicate shifting to the left.\n \nCloser to 0 is better.", - L"\n \nThe distance this weapon's muzzle will shift\nvertically between each and every bullet in a\nburst or autofire volley.\n \nPositive numbers indicate shifting upwards.\nNegative numbers indicate shifting downwards.\n \nCloser to 0 is better.", + L"\n \nThe distance this weapon's muzzle will shift\nhorizontally between each and every bullet in a\nburst or autofire volley.\n \nPositive numbers indicate shifting to the right.\nNegative numbers indicate shifting to the left.\n \nCloser to 0 is better.", // No longer used + L"\n \nThe total distance this weapon's muzzle will shift\nbetween each and every bullet in a burst or\nautofire volley, if no Counter Force is applied.\n \nLower is better.", // HEADROCK HAM 5: Altered to reflect unified number. L"\n \nIndicates the number of bullets that will be added\nto an autofire volley for every extra 5 APs\nyou spend.\n \nHigher is better.", }; @@ -6871,6 +6908,7 @@ STR16 szUDBGenExplosiveStatsTooltipText[]= { L"|D|a|m|a|g|e", L"|S|t|u|n |D|a|m|a|g|e", + L"|E|x|p|l|o|d|e|s |o|n| |I|m|p|a|c|t", // HEADROCK HAM 5 L"|B|l|a|s|t |R|a|d|i|u|s", L"|S|t|u|n |B|l|a|s|t |R|a|d|i|u|s", L"|N|o|i|s|e |B|l|a|s|t |R|a|d|i|u|s", @@ -6885,6 +6923,11 @@ STR16 szUDBGenExplosiveStatsTooltipText[]= L"|S|m|o|k|e |E|n|d |R|a|d|i|u|s", L"|I|n|c|e|n|d|i|a|r|y |E|n|d |R|a|d|i|u|s", L"|E|f|f|e|c|t |D|u|r|a|t|i|o|n", + // HEADROCK HAM 5: Fragmentation + L"|N|u|m|b|e|r |o|f |F|r|a|g|m|e|n|t|s", + L"|D|a|m|a|g|e |p|e|r |F|r|a|g|m|e|n|t", + L"|F|r|a|g|m|e|n|t |R|a|n|g|e", + // HEADROCK HAM 5: End Fragmentations L"|L|o|u|d|n|e|s|s", L"|V|o|l|a|t|i|l|i|t|y", }; @@ -6893,6 +6936,7 @@ STR16 szUDBGenExplosiveStatsExplanationsTooltipText[]= { L"\n \nThe amount of damage caused by this explosive.\n \nNote that blast-type explosives deliver this damage\nonly once (when they go off), while prolonged effect\nexplosives deliver this amount of damage every turn until the\neffect dissipates.\n \nHigher is better.", L"\n \nThe amount of non-lethal (stun) damage caused\nby this explosive.\n \nNote that blast-type explosives deliver their damage\nonly once (when they go off), while prolonged effect\nexplosives deliver this amount of stun damage every\nturn until the effect dissipates.\n \nHigher is better.", + L"\n \nThis explosive will not bounce around -\nit will explode as soon as it hits any obstacle.", // HEADROCK HAM 5 L"\n \nThis is the radius of the explosive blast caused by\nthis explosive item.\n \nTargets will suffer less damage the further they are\nfrom the center of the explosion.\n \nHigher is better.", L"\n \nThis is the radius of the stun-blast caused by\nthis explosive item.\n \nTargets will suffer less damage the further they are\nfrom the center of the blast.\n \nHigher is better.", L"\n \nThis is the distance that the noise from this\ntrap will travel. Soldiers within this distance\nare likely to hear the noise and be alerted.\n \nHigher is better.", @@ -6907,8 +6951,14 @@ STR16 szUDBGenExplosiveStatsExplanationsTooltipText[]= L"\n \nThis is the final radius of the smoke released\nby this explosive item before it dissipates.\n \nEnemies caught within the radius will suffer\nthe listed damage and stun-damage each turn\n(if any), unless wearing a gas mask. More importantly,\nanyone inside the cloud becomes extremely difficult to spot,\nand also loses a large chunk of sight-range themselves.\n \nAlso note the start radius and duration\nof the effect.\n \nHigher is better.", L"\n \nThis is the final radius of the flames caused\nby this explosive item before they dissipate.\n \nEnemies caught within the radius will suffer\nthe listed damage and stun-damage each turn.\n \nAlso note the start radius and duration of the effect.\n \nHigher is better.", L"\n \nThis is the duration of the explosive effect.\n \nEach turn, the radius of the effect will grow by\none tile in every direction, until reaching\nthe listed End Radius.\n \nOnce the duration has been reached, the effect\ndissipates completely.\n \nNote that light-type explosives become SMALLER\nover time, unlike other effects.\n \nHigher is better.", + // HEADROCK HAM 5: Fragmentation + L"\n \nThis is the number of fragments that will\nbe ejected from the explosion.\n \nFragments are similar to bullets, and may hit\nanyone who's close enough to the explosion.\n \nHigher is better.", + L"\n \nThe potential amount of damage caused by each\nfragment ejected from the explosion.\n \nHigher is better.", + L"\n \nThis is the average range to which fragments\nfrom this explosion will fly.\n \nSome fragments may end up further away, or fail\nto cover the distance altogether.\n \nHigher is better.", + // HEADROCK HAM 5: End Fragmentations L"\n \nThis is the distance (in Tiles) within which\nsoldiers and mercs will hear the explosion when\nit goes off.\n \nEnemies hearing the explosion will be alerted to your\npresence.\n \nLower is better.", L"\n \nThis value represents a chance (out of 100) for this\nexplosive to spontaneously explode whenever it is damaged\n(for instance, when other explosions go off nearby).\n \nCarrying highly-volatile explosives into combat\nis therefore extremely risky and should be avoided.\n \nScale: 0-100.\nLower is better.", + }; STR16 szUDBGenSecondaryStatsTooltipText[]= @@ -7025,10 +7075,10 @@ STR16 szUDBAdvStatsTooltipText[]= L"|C|o|o|l|d|o|w|n |F|a|c|t|o|r", L"|J|a|m |T|r|e|s|h|o|l|d", L"|D|a|m|a|g|e |T|r|e|s|h|o|l|d", - L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|c|a|t|o|r", - L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|c|a|t|o|r", - L"|J|a|m |T|r|e|s|h|o|l|d |M|o|d|i|f|i|c|a|t|o|r", - L"|D|a|m|a|g|e |T|r|e|s|h|o|l|d |M|o|d|i|f|i|c|a|t|o|r", + L"|T|e|m|p|e|r|a|t|u|r|e |M|o|d|i|f|i|e|r", + L"|C|o|o|l|d|o|w|n |M|o|d|i|f|i|e|r", + L"|J|a|m |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", + L"|D|a|m|a|g|e |T|h|r|e|s|h|o|l|d |M|o|d|i|f|i|e|r", }; // Alternate tooltip text for weapon Advanced Stats. Just different wording, nothing spectacular. @@ -7158,6 +7208,29 @@ STR16 gzNCTHlabels[]= // HEADROCK HAM 4: End new UDB texts and tooltips ////////////////////////////////////////////////////// +// HEADROCK HAM 5: Screen messages for sector inventory sorting reports. +STR16 gzMapInventorySortingMessage[] = +{ + L"Finished sorting ammo into crates in sector %c%d.", + L"Finished removing attachments from items in sector %c%d.", + L"Finished ejecting ammo from weapons in sector %c%d.", + L"Finished stacking and merging all items in sector %c%d.", +}; + +STR16 gzMapInventoryFilterOptions[] = +{ + L"Show all", + L"Guns", + L"Ammo", + L"Explosives", + L"Melee Weapons", + L"Armor", + L"LBE", + L"Kits", + L"Misc. Items", + L"Hide all", +}; + // Flugente FTW 1: Temperature-based text similar to HAM 4's condition-based text. STR16 gTemperatureDesc[] = { diff --git a/Utils/popup_callback.h b/Utils/popup_callback.h index 00af54b0..8bceddc7 100644 --- a/Utils/popup_callback.h +++ b/Utils/popup_callback.h @@ -1,9 +1,12 @@ #ifndef POPUP_CALLBACK_CLASS #define POPUP_CALLBACK_CLASS + #include "sgp.h" + class popupCallback; template class popupCallbackFunction; + template class popupCallbackFunction2; class popupCallback{ public: @@ -14,6 +17,172 @@ virtual bool call(void) = 0; }; +// returns something, takes three parameters + + template + class popupCallbackFunction3: public popupCallback{ + protected: + R (*fun)(P1,P2,P3); + P1 param_1; + P2 param_2; + P3 param_3; + public: + popupCallbackFunction3(void); + popupCallbackFunction3(void * newFun, P1 param, P2 param2, P3 param3); + virtual void bind(void * newFun); + virtual bool call(void); + }; + + template + popupCallbackFunction3::popupCallbackFunction3(void){ + this->fun = 0; + this->param_1 = P1(); + this->param_2 = P2(); + this->param_3 = P3(); + }; + + template + popupCallbackFunction3::popupCallbackFunction3(void * newFun, P1 param, P2 param2, P3 param3){ + this->fun = static_cast< R(*)(P1,P2,P3)>(newFun); + this->param_1 = param; + this->param_2 = param2; + this->param_3 = param3; + }; + + template + void popupCallbackFunction3::bind(void * newFun){ + this->fun = static_cast< R(*)(P1,P2,P3)>(newFun); + }; + + template + bool popupCallbackFunction3::call(void){ + return (bool)(this->fun)(this->param_1,this->param_2,this->param_3); + }; + + // three params, no return val + + template + class popupCallbackFunction3: public popupCallback{ + protected: + void (*fun)(P1,P2,P3); + P1 param_1; + P2 param_2; + P3 param_3; + public: + popupCallbackFunction3(void); + popupCallbackFunction3(void * newFun, P1 param, P2 param2, P3 param3); + virtual void bind(void * newFun); + virtual bool call(void); + }; + + template + popupCallbackFunction3::popupCallbackFunction3(void){ + this->fun = 0; + this->param_1 = P1(); + this->param_2 = P2(); + this->param_3 = P3(); + }; + + template + popupCallbackFunction3::popupCallbackFunction3(void * newFun, P1 param, P2 param2, P3 param3){ + this->fun = static_cast< void(*)(P1,P2,P3)>(newFun); + this->param_1 = param; + this->param_2 = param2; + this->param_3 = param3; + }; + + template + void popupCallbackFunction3::bind(void * newFun){ + this->fun = static_cast< void(*)(P1,P2,P3)>(newFun); + }; + + template + bool popupCallbackFunction3::call(void){ + (this->fun)(this->param_1,this->param_2,this->param_3); + return 1; + }; + +// returns something, takes two parameters + + template + class popupCallbackFunction2: public popupCallback{ + protected: + R (*fun)(P1,P2); + P1 param_1; + P2 param_2; + public: + popupCallbackFunction2(void); + popupCallbackFunction2(void * newFun, P1 param, P2 param2); + virtual void bind(void * newFun); + virtual bool call(void); + }; + + template + popupCallbackFunction2::popupCallbackFunction2(void){ + this->fun = 0; + this->param_1 = P1(); + this->param_2 = P2(); + }; + + template + popupCallbackFunction2::popupCallbackFunction2(void * newFun, P1 param, P2 param2){ + this->fun = static_cast< R(*)(P1,P2)>(newFun); + this->param_1 = param; + this->param_2 = param2; + }; + + template + void popupCallbackFunction2::bind(void * newFun){ + this->fun = static_cast< R(*)(P1,P2)>(newFun); + }; + + template + bool popupCallbackFunction2::call(void){ + return (bool)(this->fun)(this->param_1,this->param_2); + }; + + // two params, no return val + + template + class popupCallbackFunction2: public popupCallback{ + protected: + void (*fun)(P1,P2); + P1 param_1; + P2 param_2; + public: + popupCallbackFunction2(void); + popupCallbackFunction2(void * newFun, P1 param, P2 param2); + virtual void bind(void * newFun); + virtual bool call(void); + }; + + template + popupCallbackFunction2::popupCallbackFunction2(void){ + this->fun = 0; + this->param_1 = P1(); + this->param_2 = P2(); + }; + + template + popupCallbackFunction2::popupCallbackFunction2(void * newFun, P1 param, P2 param2){ + this->fun = static_cast< void(*)(P1,P2)>(newFun); + this->param_1 = param; + this->param_2 = param2; + }; + + template + void popupCallbackFunction2::bind(void * newFun){ + this->fun = static_cast< void(*)(P1,P2)>(newFun); + }; + + template + bool popupCallbackFunction2::call(void){ + (this->fun)(this->param_1,this->param_2); + return 1; + }; + +// returns something, takes one parameter + template class popupCallbackFunction : public popupCallback{ protected: diff --git a/Utils/popup_class.cpp b/Utils/popup_class.cpp index 1f7d41b1..72bc42c0 100644 --- a/Utils/popup_class.cpp +++ b/Utils/popup_class.cpp @@ -119,6 +119,18 @@ POPUP_OPTION::POPUP_OPTION(void) this->action = 0; this->avail = 0; this->hover = 0; + + // set highlight color + this->color_highlight = FONT_WHITE; + + // unhighlighted color + this->color_foreground = FONT_LTGREEN; + + // background color + this->color_background = FONT_BLACK; + + // shaded color..for darkened text + this->color_shade = FONT_GRAY7 ; } POPUP_OPTION::~POPUP_OPTION(void) @@ -136,11 +148,23 @@ POPUP_OPTION::POPUP_OPTION(std::wstring *newName, popupCallback * newFunction) this->action = newFunction; this->avail = 0; this->hover = 0; + + // set highlight color + this->color_highlight = FONT_WHITE; + + // unhighlighted color + this->color_foreground = FONT_LTGREEN; + + // background color + this->color_background = FONT_BLACK; + + // shaded color..for darkened text + this->color_shade = FONT_GRAY7 ; } -BOOLEAN POPUP_OPTION::setName( WCHAR* newName ) +BOOLEAN POPUP_OPTION::setName( std::wstring * newName ) { - this->name = newName; + this->name = *newName; return TRUE; } @@ -170,6 +194,8 @@ BOOLEAN POPUP_OPTION::checkAvailability() { if (this->avail != 0) return ( this->avail->call() == true ); + else if ( this->action == 0 ) // options with no action are always shaded + return false; else return true; } @@ -211,12 +237,19 @@ BOOLEAN POPUP_OPTION::forceRun() ////////////////////////////////////////////////////////////////// // constructor -POPUP_SUB_POPUP_OPTION::POPUP_SUB_POPUP_OPTION(void) : POPUP_OPTION() +POPUP_SUB_POPUP_OPTION::POPUP_SUB_POPUP_OPTION(void) : POPUP_OPTION(new wstring(L"Unnamed subPopup"),NULL) //TODO: possible memmory leak! { + this->parent = NULL; this->initSubPopup(); } -POPUP_SUB_POPUP_OPTION::POPUP_SUB_POPUP_OPTION(wstring* newName, const POPUP * parent) : POPUP_OPTION(newName, 0) +POPUP_SUB_POPUP_OPTION::POPUP_SUB_POPUP_OPTION(wstring* name) : POPUP_OPTION(name, NULL) +{ + this->parent = NULL; + this->initSubPopup(); +} + +POPUP_SUB_POPUP_OPTION::POPUP_SUB_POPUP_OPTION(wstring* newName, const POPUP * parent) : POPUP_OPTION(newName, NULL) { this->parent = parent; this->initSubPopup(); @@ -230,10 +263,13 @@ POPUP_SUB_POPUP_OPTION::~POPUP_SUB_POPUP_OPTION(void) void POPUP_SUB_POPUP_OPTION::showPopup() { - // check if it's available at this time - if ( this->checkAvailability() ) + // check if it's available at this time, if there is an avail function set + if ( !this->AvailabilityFunctionSet() || this->checkAvailability() ) { - if (!this->customPositionSet) this->positionSubPopup(); + if (!this->customPositionSet || this->customRule == POPUP_POSITION_RELATIVE) { + this->positionSubPopup(); + } + this->subPopup->show(); } } @@ -247,10 +283,14 @@ void POPUP_SUB_POPUP_OPTION::positionSubPopup() { INT16 x,y; -// x = this->parent->Position.iX + this->parent->Dimensions.iLeft + this->parent->Dimensions.iRight + 10; - x = this->parent->X + this->parent->Dimensions.iLeft + this->parent->Dimensions.iRight + 10; -// y = this->parent->Position.iY + 10; - y = this->parent->Y + 10; + if( this->customPositionSet ) + { + x = this->parent->X + this->parent->Dimensions.iLeft + this->parent->Dimensions.iRight + this->customX; + y = this->parent->Y + this->customY; + } else { + x = this->parent->X + this->parent->Dimensions.iLeft + this->parent->Dimensions.iRight + 10; + y = this->parent->Y + 10; + } this->subPopup->setPosition(x,y); } @@ -263,20 +303,62 @@ BOOLEAN POPUP_SUB_POPUP_OPTION::run() return TRUE; } -BOOLEAN POPUP_SUB_POPUP_OPTION::setPopupPosition(UINT16 x, UINT16 y, UINT8 positioningRule){ +BOOLEAN POPUP_SUB_POPUP_OPTION::setPopupPosition(UINT16 x, UINT16 y, INT8 positioningRule){ this->customPositionSet = true; if (positioningRule != POPUP_POSITION_RELATIVE){ return this->subPopup->setPosition(x,y,positioningRule); } else { - return this->subPopup->setPosition(this->parent->X + x, this->parent->Y + y,POPUP_POSITION_TOP_LEFT); + this->customX = x; + this->customY = y; + this->customRule = positioningRule; + return this->subPopup->setPosition(this->parent->X + this->parent->Dimensions.iRight + x, this->parent->Y + y,POPUP_POSITION_TOP_LEFT); } } + + + +static void shadeOpenSubPopup( POPUP_SUB_POPUP_OPTION * opt ){ + // make the option yellow while its open + SetCurrentBox( opt->parent->getBoxId() ); + SetStringForeground( opt->stringHandle, FONT_YELLOW); +} + +extern void RenderTeamRegionBackground(); +static void unShadeOpenSubPopup( POPUP_SUB_POPUP_OPTION * opt ){ + // restore the option's color + SetCurrentBox( opt->parent->getBoxId() ); + SetStringForeground( opt->stringHandle, FONT_LTGREEN); + + // if in mapscreen, redraw the UI + if( guiCurrentScreen == MAP_SCREEN ){ + fTeamPanelDirty = TRUE; + fMapPanelDirty = TRUE; + RenderTeamRegionBackground(); + } + +} + void POPUP_SUB_POPUP_OPTION::initSubPopup() { this->subPopup = new POPUP( (CHAR*) wstring(this->name).c_str() ); + this->subPopup->setCallback(POPUP_CALLBACK_SHOW,new popupCallbackFunction( &shadeOpenSubPopup, this )); + this->subPopup->setCallback(POPUP_CALLBACK_HIDE,new popupCallbackFunction( &unShadeOpenSubPopup, this )); + this->customPositionSet = false; + + // set highlight color + this->color_highlight = FONT_WHITE; + + // unhighlighted color + this->color_foreground = FONT_LTGREEN; + + // background color + this->color_background = FONT_BLACK; + + // shaded color..for darkened text + this->color_shade = FONT_GRAY7 ; } void POPUP_SUB_POPUP_OPTION::destroySubPopup() @@ -508,7 +590,7 @@ BOOLEAN POPUP::addOption(POPUP_OPTION &option) if (this->optionCount < POPUP_MAX_OPTIONS) { this->options.push_back( &option ); - optionCount++; + this->optionCount++; return TRUE; } else @@ -536,6 +618,19 @@ POPUP * POPUP::addSubMenuOption(wstring * name) return NULL; } +BOOL POPUP::addSubMenuOption( POPUP_SUB_POPUP_OPTION* sub ) +{ + if (this->subPopupOptionCount < POPUP_MAX_SUB_POPUPS) + { + sub->parent = this; + this->subPopupOptions.push_back( sub ); + this->subPopupOptionCount++; + return TRUE; + } + else + return FALSE; +} + POPUP_SUB_POPUP_OPTION * POPUP::getSubPopupOption(UINT8 n) { if (n < this->subPopupOptions.size() && this->subPopupOptions[n]) @@ -544,6 +639,59 @@ POPUP_SUB_POPUP_OPTION * POPUP::getSubPopupOption(UINT8 n) return NULL; } +void POPUP::recalculateWidth(void){ + + this->Dimensions.iRight = this->getCurrentWidth(); + +} + +INT16 POPUP::getCurrentWidth(void){ + INT16 longestString = 0, longestStringTmp; + + CHAR16 sString[ 128 ]; + + if (this->subPopupOptionCount > 0) + + for(std::vector::iterator cOption=this->subPopupOptions.begin(); cOption != this->subPopupOptions.end(); ++cOption) + { + swprintf( sString, (*cOption)->name.c_str() ); + longestStringTmp = StringPixLength( sString, MAP_SCREEN_FONT); + if( longestString < longestStringTmp ) longestString = longestStringTmp; + } + + if (this->optionCount > 0) + for(std::vector::iterator cOption=this->options.begin(); cOption != this->options.end(); ++cOption) + { + swprintf( sString, (*cOption)->name.c_str() ); + longestStringTmp = StringPixLength( sString, MAP_SCREEN_FONT); + if( longestString < longestStringTmp ) longestString = longestStringTmp; + } + + return longestString + 10; // 10+ marginLeft(6) + marginRight(4) +} + +INT16 POPUP::getCurrentHeight(void){ + + // linespace = 2; margin = 6+4 + return ( this->optionCount + this->subPopupOptionCount ) * ( GetFontHeight(MAP_SCREEN_FONT) + 2 ) +10; + +} + +SGPRect POPUP::getBoxDimensions(){ + SGPRect tmp; + + GetBoxSize( this->boxId, &tmp ); + + return tmp; +} + +SGPPoint POPUP::getBoxPosition(){ + SGPPoint tmp; + + GetBoxPosition( this->boxId, &tmp ); + + return tmp; +} BOOLEAN POPUP::setPosition(UINT16 x, UINT16 y, UINT8 positioningRule ) { @@ -616,6 +764,10 @@ BOOLEAN POPUP::show() return TRUE; } +void POPUP::hideAfter(){ + this->hideAfterRun = TRUE; +} + BOOLEAN POPUP::hide() { if (!this->PopupVisible) { @@ -659,12 +811,22 @@ BOOLEAN POPUP::hide() BOOLEAN POPUP::callOption(int optIndex) { + this->hideAfterRun = FALSE; + if ( this->optionCount > 0 - && optIndex < 32 + && optIndex < POPUP_MAX_OPTIONS && optIndex < optionCount && optIndex >= 0) { - return this->options[optIndex]->run(); + BOOLEAN callbackStatus = this->options[optIndex]->run(); + + if(!this->hideAfterRun) + { + return callbackStatus; + } else + { + return this->hide(); + } } else return FALSE; @@ -857,6 +1019,8 @@ void POPUP::AddSubPopupStrings() // make sure it is unhighlighted UnHighLightLine(hStringHandle); + // update options string handle for setting colors + (*cOption)->stringHandle = hStringHandle; } } @@ -874,12 +1038,15 @@ void POPUP::AddOptionStrings() // make sure it is unhighlighted UnHighLightLine(hStringHandle); + // update options string handle for setting colors + (*cOption)->stringHandle = hStringHandle; } } void POPUP::UpdateTextProperties() { + // set font type SetBoxFont(this->boxId, MAP_SCREEN_FONT); @@ -895,9 +1062,30 @@ void POPUP::UpdateTextProperties() // shaded color..for darkened text SetBoxShade( this->boxId, FONT_GRAY7 ); - // is this for the second row ? + // alt shaded color SetBoxSecondaryShade( this->boxId, FONT_YELLOW ); + + if (this->subPopupOptionCount > 0) + for(std::vector::iterator cOption=this->subPopupOptions.begin(); cOption != this->subPopupOptions.end(); ++cOption) + { + SetStringForeground( (*cOption)->stringHandle, (*cOption)->color_foreground); + SetStringBackground( (*cOption)->stringHandle, (*cOption)->color_background); + SetStringHighLight( (*cOption)->stringHandle, (*cOption)->color_highlight); + SetStringShade( (*cOption)->stringHandle, (*cOption)->color_shade); + } + + + if (this->optionCount > 0) + for(std::vector::iterator cOption=this->options.begin(); cOption != this->options.end(); ++cOption) + { + SetStringForeground( (*cOption)->stringHandle, (*cOption)->color_foreground); + SetStringBackground( (*cOption)->stringHandle, (*cOption)->color_background); + SetStringHighLight( (*cOption)->stringHandle, (*cOption)->color_highlight); + SetStringShade( (*cOption)->stringHandle, (*cOption)->color_shade); + } + + // resize box to text ResizeBoxToText( this->boxId ); } @@ -1294,6 +1482,7 @@ void POPUP::MenuBtnCallBack( MOUSE_REGION * pRegion, INT32 iReason ) && iValue >= 0 && this->subPopupOptions[iValue] != NULL) { + this->recalculateWidth(); this->subPopupOptions[iValue]->run(); } } @@ -1349,24 +1538,34 @@ void POPUP::MenuMvtCallBack(MOUSE_REGION * pRegion, INT32 iReason ) void POPUP::HandleShadingOfLines( void ) { - UINT8 i = 0; - // check if valid if( ( this->PopupVisible == FALSE ) || ( this->boxId == - 1 ) ) { return; } - //for (i=0; i < this->optionCount; i++) + UINT8 i = 0; + // subPopups with no options are shaded + for(std::vector::iterator cOption=this->subPopupOptions.begin(); cOption != this->subPopupOptions.end(); ++cOption) + { + if ( (*cOption)->subPopup->optionCount + (*cOption)->subPopup->subPopupOptionCount > 0 ) + UnShadeStringInBox( this->boxId, i); + else + ShadeStringInBox( this->boxId, i); + i++; + } + + i = 0; + // shade strings past the this->subPopupOptionCount offset for(std::vector::iterator cOption=this->options.begin(); cOption != this->options.end(); ++cOption) { if ( (*cOption)->checkAvailability() ) - UnShadeStringInBox( this->boxId, i+this->subPopupOptionCount); // workaround - sub-popups don't check - else // for availability + UnShadeStringInBox( this->boxId, i+this->subPopupOptionCount); + else ShadeStringInBox( this->boxId, i+this->subPopupOptionCount); i++; } - + return; } diff --git a/Utils/popup_class.h b/Utils/popup_class.h index e258409c..c2f6e4d7 100644 --- a/Utils/popup_class.h +++ b/Utils/popup_class.h @@ -1,12 +1,13 @@ #ifndef POPUP_CLASS #define POPUP_CLASS - #include #include "popup_callback.h" + #include "types.h" + #include "sgp.h" - #define MAX_POPUPS 8 - #define POPUP_MAX_SUB_POPUPS 2 - #define POPUP_MAX_OPTIONS 32 + #define MAX_POPUPS 32 + #define POPUP_MAX_SUB_POPUPS 24 + #define POPUP_MAX_OPTIONS 96 #define MAX_REGIONS_IN_INDEX MAX_POPUPS*POPUP_MAX_OPTIONS*POPUP_MAX_SUB_POPUPS #define REGION_SUB_POPUP 1 @@ -30,7 +31,7 @@ #define POPUP_POSITION_BOTTOM_RIGHT 3 // user defined point (position) is the bottom right corner of the popup #define POPUP_POSITION_BOTTOM_LEFT 4 // user defined point (position) is the bottom left corner of the popup - #define POPUP_POSITION_RELATIVE 0 // only used for sub-popups, left corner is relative to parent popup's point of origin + #define POPUP_POSITION_RELATIVE -1 // only used for sub-popups, left corner is relative to parent popup's point of origin class POPUP_OPTION; class POPUP_SUB_POPUP_OPTION; @@ -69,7 +70,7 @@ POPUP_OPTION(std::wstring* name, popupCallback* newFunction); // constructor ~POPUP_OPTION(); // destructor // setup - BOOLEAN setName(WCHAR* name); + BOOLEAN setName(std::wstring * name); BOOLEAN setAction(popupCallback*fun); BOOLEAN setAvail(popupCallback *fun); BOOLEAN setHover(popupCallback *fun); @@ -89,6 +90,13 @@ popupCallback * action; popupCallback * avail; popupCallback * hover; + + UINT8 color_foreground; + UINT8 color_background; + UINT8 color_highlight; + UINT8 color_shade; + + UINT32 stringHandle; }; @@ -110,12 +118,12 @@ void destroySubPopup(); void positionSubPopup(); - BOOLEAN setPopupPosition(UINT16 x, UINT16 y, UINT8 positioningRule = POPUP_POSITION_RELATIVE ); + BOOLEAN setPopupPosition(UINT16 x, UINT16 y, INT8 positioningRule = POPUP_POSITION_RELATIVE ); BOOLEAN customPositionSet; - INT32 customX; - INT32 customY; - UINT8 customRule; + UINT32 customX; + UINT32 customY; + INT8 customRule; POPUP * subPopup; const POPUP * parent; @@ -140,6 +148,7 @@ BOOLEAN delOption(UINT8 optIndex); POPUP* addSubMenuOption(std::wstring * name); + BOOL addSubMenuOption(POPUP_SUB_POPUP_OPTION* sub); /*INT16 findFreeSubMenuOptionIndex();*/ POPUP_SUB_POPUP_OPTION * getSubPopupOption(UINT8 n); @@ -148,18 +157,26 @@ // usage BOOLEAN show(void); BOOLEAN hide(void); + void hideAfter(void); // called by options, closes popup after running callback BOOLEAN toggle(void); BOOLEAN refresh(void); BOOLEAN forceDraw(void); BOOLEAN callOption(int optIndex); - INT32 getBoxId(){ + INT32 getBoxId() const { return this->boxId; } + // gets the final dimensions/position of the box, works only after it has been displayed + SGPRect getBoxDimensions(); + SGPPoint getBoxPosition(); + BOOLEAN setCallback(UINT8 type, popupCallback * callback); BOOLEAN isCallbackSet(UINT8 type); + void recalculateWidth(void); + INT16 getCurrentWidth(void); // calculated the width based on the longest option name + INT16 getCurrentHeight(void); ///////////////////////// // public variables public: @@ -178,6 +195,7 @@ void setInitialValues(void); BOOLEAN addToIndex( void ); BOOLEAN removeFromIndex( void ); + BOOLEAN hideAfterRun; // Box init functions BOOLEAN CreateDestroyPopUpBoxes(void); @@ -202,6 +220,7 @@ void AdjustMouseRegions( void ); void RepositionMouseRegions( void ); + public: // MSYS Callbacks void MenuMvtCallBack(MOUSE_REGION * pRegion, INT32 iReason ); @@ -258,7 +277,7 @@ INT32 boxId; // which corner of the popup should be aligned with the supplied coordinates ? - UINT8 positioningRule; + INT8 positioningRule; // the x,y position of the pop up in tactical diff --git a/Utils/popup_definition.cpp b/Utils/popup_definition.cpp new file mode 100644 index 00000000..25b3f0dd --- /dev/null +++ b/Utils/popup_definition.cpp @@ -0,0 +1,235 @@ + #include "popup_class.h" + #include "popup_callback.h" + #include "sgp.h" + + #include "popup_definition.h" + #include "Interface Items.h" + + // for getting psoldier + #include "Map Screen Interface.h" + #include "Map Screen Interface Map.h" + #include "Overhead.h" + +////////////////////////////////////// +// popupDef +////////////////////////////////////// + + popupDef::popupDef(){} + + popupDef::~popupDef(){ + for(std::vector::iterator content=this->content.begin(); content != this->content.end(); ++content) + { + delete *content; + } + } + + BOOL popupDef::applyToBox(POPUP* popup){ + for(std::vector::iterator content=this->content.begin(); content != this->content.end(); ++content) + { + if ( (*content)->addToBox( popup ) != TRUE ) return FALSE; + } + + return TRUE; + } + + BOOL popupDef::addOption(std::wstring* name, UINT16 callbackId, UINT16 availId){ + // TODO: check for vaid callbacl/avail ID + + this->content.push_back( new popupDefOption(name,callbackId,availId) ); + + return TRUE; + } + + popupDef * popupDef::addSubPopup(std::wstring* name){ + + popupDefSubPopupOption * sub = new popupDefSubPopupOption(name); + + this->content.push_back( sub ); + + return sub->getSubDef(); + } + + BOOL popupDef::addSubPopup(popupDefSubPopupOption* sub){ + // TODO: add check for max options + this->content.push_back( sub ); + + return TRUE; + }; + + BOOL popupDef::addGenerator(UINT16 id){ + // TODO: check for valid generator id + + this->content.push_back( new popupDefContentGenerator(id) ); + + return TRUE; + } + + +////////////////////////////////////// +// popupDefContent +////////////////////////////////////// + + popupDefContent::popupDefContent(){} + popupDefContent::~popupDefContent(){} + + +////////////////////////////////////// +// popupDefOption helpers +////////////////////////////////////// + + static BOOL setPopupDefCallback( POPUP_OPTION * opt, UINT16 callbackId ){ + + // TODO + opt->setAction(NULL); + + return TRUE; + + } + + static BOOL setPopupDefAvail( POPUP_OPTION * opt, UINT16 callbackId ){ + + // TODO + opt->setAvail(NULL); + + return TRUE; + + } + +////////////////////////////////////// +// popupDefOption +////////////////////////////////////// + /* defined in header file + popupDefOption::popupDefOption(){} + popupDefOption::popupDefOption( std::wstring* name, UINT16 callbackId, UINT16 availId ){} + + ~popupDefOption::popupDefOption(){} + */ + BOOL popupDefOption::addToBox(POPUP * popup){ + + POPUP_OPTION * opt = new POPUP_OPTION(); + + opt->setName( this->name ); + if ( !setPopupDefCallback(opt, this->callbackId) + || !setPopupDefAvail(opt, this->availId) ) + { + delete opt; + return false; + } + + + return popup->addOption(*opt); + } + + +////////////////////////////////////// +// popupDefSubPopupOption +////////////////////////////////////// + /* defined in header file + popupDefSubPopupOption::popupDefSubPopupOption(){} + popupDefSubPopupOption::popupDefSubPopupOption( std::wstring* name ){} + + popupDefSubPopupOption::~popupDefSubPopupOption(){} + */ + BOOL popupDefSubPopupOption::addToBox(POPUP * popup){ + + POPUP_SUB_POPUP_OPTION * sub = new POPUP_SUB_POPUP_OPTION( this->name ); + + if( !this->content->applyToBox( sub->subPopup ) ){ + delete sub; + return false; + } + + return popup->addSubMenuOption(sub); + } + + +////////////////////////////////////// +// popupDefContentGenerator helpers +////////////////////////////////////// + + /* + void addArmorToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addLBEToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addWeaponsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addWeaponGroupsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addBombsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addFaceGearToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + + void addRifleGrenadesToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addRocketAmmoToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + void addDrugsToPocketPopup( SOLDIERTYPE *pSoldier, INT16 sPocket, POPUP* popup ); + */ + + static BOOL applyPopupContentGenerator( POPUP * popup, UINT16 generatorId ){ + + SOLDIERTYPE *pSoldier; + GetSoldier( &pSoldier, gCharactersList[ bSelectedInfoChar ].usSolID ); + + switch(generatorId){ + case popupGenerators::dummy: + popup->addOption(new std::wstring( L"Dummy generator" ),NULL); + break; + + case popupGenerators::addArmor: + addArmorToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addLBE: + addLBEToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addWeapons: + addWeaponsToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addWeaponGroups: + addWeaponGroupsToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addGrenades: + addGrenadesToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addBombs: + addBombsToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addFaceGear: + addFaceGearToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addAmmo: + addAmmoToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addRifleGrenades: + addRifleGrenadesToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addRocketAmmo: + addRocketAmmoToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addMisc: + addMiscToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + case popupGenerators::addKits: + addKitsToPocketPopup( pSoldier, gsPocketUnderCursor, popup ); + break; + + default: + return FALSE; + } + + return TRUE; + + } + +////////////////////////////////////// +// popupDefContentGenerator +////////////////////////////////////// + /* defined in header file + popupDefContentGenerator::popupDefContentGenerator(){} + popupDefContentGenerator::popupDefContentGenerator( UINT16 generatorId ){} + + popupDefContentGenerator::~popupDefContentGenerator(){} + */ + + BOOL popupDefContentGenerator::addToBox(POPUP * popup){ + + return applyPopupContentGenerator( popup, this->generatorId ); + + } + \ No newline at end of file diff --git a/Utils/popup_definition.h b/Utils/popup_definition.h new file mode 100644 index 00000000..2b687f8f --- /dev/null +++ b/Utils/popup_definition.h @@ -0,0 +1,109 @@ +#ifndef POPUP_DEFINITION + #define POPUP_DEFINITION + +#include "sgp.h" +#include "popup_class.h" +#include "popup_callback.h" + +namespace popupGenerators{ + + enum{ + dummy = 1, + addArmor, + addLBE, + addWeapons, + addWeaponGroups, + addGrenades, + addBombs, + addFaceGear, + addAmmo, + addRifleGrenades, + addRocketAmmo, + addMisc, + addKits + }; + +}; + +class popupDef; +class popupDefContent; + +class popupDefOption; +class popupDefSubPopupOption; +class popupDefContentGenerator; + +class popupDef{ +public: + popupDef(); + ~popupDef(); + + BOOL applyToBox(POPUP* popup); + + BOOL addOption(std::wstring* name, UINT16 callbackId, UINT16 availId); + + popupDef * addSubPopup(std::wstring* name); + BOOL addSubPopup(popupDefSubPopupOption* sub); + + BOOL addGenerator(UINT16 id); +protected: + std::vector content; +}; + +class popupDefContent{ +public: + popupDefContent(); + ~popupDefContent(); + + virtual BOOL addToBox(POPUP * popup) = 0; + +}; + +class popupDefOption : public popupDefContent{ +public: + popupDefOption() : name( new std::wstring(L"Unnamed Option") ), callbackId(0), availId(0){}; + popupDefOption( std::wstring* name, UINT16 callbackId, UINT16 availId ) : name( name ), callbackId(callbackId), availId(availId){}; + + ~popupDefOption(){ delete this->name; }; + + BOOL addToBox(POPUP * popup); + +protected: + std::wstring* name; + UINT16 callbackId; + UINT16 availId; + +}; + +class popupDefSubPopupOption : public popupDefContent{ +public: + popupDefSubPopupOption() : name( new std::wstring(L"Unnamed Submenu") ){ this->content = new popupDef(); }; + popupDefSubPopupOption( std::wstring* name ) : name( name ){ this->content = new popupDef(); }; + + ~popupDefSubPopupOption(){ delete this->name; delete this->content; }; + + BOOL addToBox(POPUP * popup); + void rename( std::wstring* name ){ + delete this->name; // lets just hope nothing else was using this string. TODO: use smart pointer + this->name = name; + }; + popupDef * getSubDef(){ return this->content; }; + +protected: + std::wstring* name; + popupDef * content; +}; + +class popupDefContentGenerator: public popupDefContent{ +public: + popupDefContentGenerator() : generatorId(0){}; + popupDefContentGenerator( UINT16 generatorId ) : generatorId( generatorId ){}; + + ~popupDefContentGenerator(); + + BOOL addToBox(POPUP * popup); + +protected: + UINT16 generatorId; +}; + +#endif \ No newline at end of file