#ifdef PRECOMPILEDHEADERS #include "Tactical All.h" #else #include "Types.h" #include "stdlib.h" #include "Arms Dealer Init.h" #include "String.h" #include "Debug.h" #include "Random.h" #include "Weapons.h" #include "FileMan.h" #include "Game Clock.h" #include "ArmsDealerInvInit.h" #include "Message.h" #include "soldier profile.h" #include "Handle Items.h" #endif // To reduce memory fragmentation from frequent MemRealloc(), we allocate memory for more than one special slot each // time we run out of space. Odds are that if we need one, we'll need another soon. #define SPECIAL_ITEMS_ALLOCED_AT_ONCE 3 // Once allocated, the special item slots remain allocated for the duration of the game, or until the dealer dies. // This is a little bit wasteful, but saves an awful lot of hassles, and avoid unnecessary memory fragmentation #define MIN_REPAIR_TIME_IN_MINUTES 15 // minutes #define MIN_REPAIR_COST 10 // dollars // price classes #define PRICE_CLASS_JUNK 0 #define PRICE_CLASS_CHEAP 1 #define PRICE_CLASS_EXPENSIVE 2 void ConvertCreatureBloodToElixir( void ); UINT8 gubLastSpecialItemAddedAtElement = 255; // THIS STRUCTURE HAS UNCHANGING INFO THAT DOESN'T GET SAVED/RESTORED/RESET // TODO: externalize ARMS_DEALER_INFO ArmsDealerInfo[ NUM_ARMS_DEALERS ] = { //Buying Selling Merc ID# Type Initial Flags //Price Price Of Cash //Modifier Modifier Dealer /* Tony */ { 0.75f, 1.25f, TONY, ARMS_DEALER_BUYS_SELLS, 15000, ARMS_DEALER_SOME_USED_ITEMS | ARMS_DEALER_GIVES_CHANGE }, /* Franz Hinkle */ { 1.0f, 1.5f, FRANZ, ARMS_DEALER_BUYS_SELLS, 5000, ARMS_DEALER_SOME_USED_ITEMS | ARMS_DEALER_GIVES_CHANGE }, /* Keith Hemps */ { 0.75f, 1.0f, KEITH, ARMS_DEALER_BUYS_SELLS, 1500, ARMS_DEALER_ONLY_USED_ITEMS | ARMS_DEALER_GIVES_CHANGE }, /* Jake Cameron */ { 0.8f, 1.1f, JAKE, ARMS_DEALER_BUYS_SELLS, 2500, ARMS_DEALER_ONLY_USED_ITEMS | ARMS_DEALER_GIVES_CHANGE }, /* Gabby Mulnick*/ { 1.0f, 1.0f, GABBY, ARMS_DEALER_BUYS_SELLS, 3000, ARMS_DEALER_GIVES_CHANGE }, /* Devin Connell*/ { 0.75f, 1.25f, DEVIN, ARMS_DEALER_SELLS_ONLY, 5000, ARMS_DEALER_GIVES_CHANGE }, /* Howard Filmore*/ { 1.0f, 1.0f, HOWARD, ARMS_DEALER_SELLS_ONLY, 3000, ARMS_DEALER_GIVES_CHANGE }, /* Sam Rozen */ { 1.0f, 1.0f, SAM, ARMS_DEALER_SELLS_ONLY, 3000, ARMS_DEALER_GIVES_CHANGE }, /* Frank */ { 1.0f, 1.0f, FRANK, ARMS_DEALER_SELLS_ONLY, 500, ARMS_DEALER_ACCEPTS_GIFTS }, /* Bar Bro 1 */ { 1.0f, 1.0f, HERVE, ARMS_DEALER_SELLS_ONLY, 250, ARMS_DEALER_ACCEPTS_GIFTS }, /* Bar Bro 2 */ { 1.0f, 1.0f, PETER, ARMS_DEALER_SELLS_ONLY, 250, ARMS_DEALER_ACCEPTS_GIFTS }, /* Bar Bro 3 */ { 1.0f, 1.0f, ALBERTO, ARMS_DEALER_SELLS_ONLY, 250, ARMS_DEALER_ACCEPTS_GIFTS }, /* Bar Bro 4 */ { 1.0f, 1.0f, CARLO, ARMS_DEALER_SELLS_ONLY, 250, ARMS_DEALER_ACCEPTS_GIFTS }, /* Micky O'Brien*/ { 1.0f, 1.4f, MICKY, ARMS_DEALER_BUYS_ONLY, 10000, ARMS_DEALER_HAS_NO_INVENTORY | ARMS_DEALER_GIVES_CHANGE }, //Repair Repair //Speed Cost /* Arnie Brunzwell*/{ 0.1f, 0.8f, ARNIE, ARMS_DEALER_REPAIRS, 1500, ARMS_DEALER_HAS_NO_INVENTORY | ARMS_DEALER_GIVES_CHANGE }, /* Fredo */ { 0.6f, 0.6f, FREDO, ARMS_DEALER_REPAIRS, 1000, ARMS_DEALER_HAS_NO_INVENTORY | ARMS_DEALER_GIVES_CHANGE }, /* Perko */ { 1.0f, 0.4f, PERKO, ARMS_DEALER_REPAIRS, 1000, ARMS_DEALER_HAS_NO_INVENTORY | ARMS_DEALER_GIVES_CHANGE }, /* Elgin */ { 1.0f, 1.0f, DRUGGIST, ARMS_DEALER_SELLS_ONLY, 500, ARMS_DEALER_ACCEPTS_GIFTS }, /* Manny */ { 1.0f, 1.0f, MANNY, ARMS_DEALER_SELLS_ONLY, 500, ARMS_DEALER_ACCEPTS_GIFTS }, }; // THESE GET SAVED/RESTORED/RESET ARMS_DEALER_STATUS gArmsDealerStatus[ NUM_ARMS_DEALERS ]; DEALER_ITEM_HEADER gArmsDealersInventory[ NUM_ARMS_DEALERS ][ MAXITEMS ]; void InitializeOneArmsDealer( UINT8 ubArmsDealer ); void AddAmmoToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubShotsLeft ); void AddItemToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, UINT8 ubHowMany ); void AddSpecialItemToArmsDealerInventoryAtElement( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubElement, SPECIAL_ITEM_INFO *pSpclItemInfo ); void RemoveRandomItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany ); void DailyCheckOnItemQuantities(); void SimulateArmsDealerCustomer(); BOOLEAN AdjustCertainDealersInventory(); void LimitArmsDealersInventory( UINT8 ubArmsDealer, UINT32 uDealerItemType, UINT8 ubMaxNumberOfItemType ); void GuaranteeAtLeastOneItemOfType( UINT8 ubArmsDealer, UINT32 uiDealerItemType ); void GuaranteeAtLeastXItemsOfIndex( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany ); BOOLEAN AllocMemsetSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem, UINT8 ubElementsNeeded ); BOOLEAN ResizeSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem, UINT8 ubElementsNeeded ); void FreeSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem ); void ArmsDealerGetsFreshStock( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubNumItems ); BOOLEAN ItemContainsLiquid( UINT16 usItemIndex ); UINT8 DetermineDealerItemCondition( UINT8 ubArmsDealer, UINT16 usItemIndex ); BOOLEAN IsItemInfoSpecial( SPECIAL_ITEM_INFO *pSpclItemInfo ); BOOLEAN DoesItemAppearInDealerInventoryList( UINT8 ubArmsDealer, UINT16 usItemIndex, BOOLEAN fPurchaseFromPlayer ); BOOLEAN LoadIncompleteArmsDealersStatus( HWFILE hFile, BOOLEAN fIncludesElgin, BOOLEAN fIncludesManny ); //INT16 GetSpecialItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo ); void GuaranteeMinimumAlcohol( UINT8 ubArmsDealer ); BOOLEAN ItemIsARocketRifle( INT16 sItemIndex ); BOOLEAN GetArmsDealerShopHours( UINT8 ubArmsDealer, UINT32 *puiOpeningTime, UINT32 *puiClosingTime ); void InitAllArmsDealers() { UINT8 ubArmsDealer; //Memset all dealers' status tables to zeroes memset( gArmsDealerStatus, 0, sizeof( gArmsDealerStatus ) ); //Memset all dealers' inventory tables to zeroes memset( gArmsDealersInventory, 0, sizeof( gArmsDealersInventory ) ); //Initialize the initial status & inventory for each of the arms dealers for( ubArmsDealer = 0; ubArmsDealer < NUM_ARMS_DEALERS; ubArmsDealer++ ) { InitializeOneArmsDealer( ubArmsDealer ); } //make sure certain items are in stock and certain limits are respected AdjustCertainDealersInventory( ); } void InitializeOneArmsDealer( UINT8 ubArmsDealer ) { UINT16 usItemIndex; UINT8 ubNumItems=0; memset( &( gArmsDealerStatus[ ubArmsDealer ] ), 0, sizeof( ARMS_DEALER_STATUS ) ); memset( &( gArmsDealersInventory[ ubArmsDealer ] ), 0, sizeof( DEALER_ITEM_HEADER ) * MAXITEMS ); //Reset the arms dealers cash on hand to the default initial value gArmsDealerStatus[ ubArmsDealer ].uiArmsDealersCash = ArmsDealerInfo[ ubArmsDealer ].iInitialCash; //if the arms dealer isn't supposed to have any items (includes all repairmen) if( ArmsDealerInfo[ ubArmsDealer ].uiFlags & ARMS_DEALER_HAS_NO_INVENTORY ) { return; } //loop through all the item types for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //Can the item be sold by the arms dealer if( CanDealerTransactItem( ubArmsDealer, usItemIndex, FALSE ) ) { //Setup an initial amount for the items (treat items as new, how many are used isn't known yet) ubNumItems = DetermineInitialInvItems( ubArmsDealer, usItemIndex, GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ), FALSE ); //if there are any initial items if( ubNumItems > 0 ) { ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, ubNumItems ); } } } } void ShutDownArmsDealers() { UINT8 ubArmsDealer; UINT16 usItemIndex; // loop through all the dealers for( ubArmsDealer=0; ubArmsDealer 0 ) { FreeSpecialItemArray( &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ] ); } } } } BOOLEAN SaveArmsDealerInventoryToSaveGameFile( HWFILE hFile ) { UINT32 uiNumBytesWritten; UINT8 ubArmsDealer; UINT16 usItemIndex; //Save the arms dealers status if (!FileWrite( hFile, gArmsDealerStatus, sizeof( gArmsDealerStatus ), &uiNumBytesWritten )) { return( FALSE ); } //save the dealers inventory item headers (all at once) if (!FileWrite( hFile, gArmsDealersInventory, sizeof( gArmsDealersInventory ), &uiNumBytesWritten )) { return( FALSE ); } //loop through all the dealers inventories for( ubArmsDealer=0; ubArmsDealer 0 ) { if (!FileWrite( hFile, &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[0], sizeof( DEALER_SPECIAL_ITEM ) * gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced, &uiNumBytesWritten )) { return( FALSE ); } } } } return( TRUE ); } BOOLEAN LoadArmsDealerInventoryFromSavedGameFile( HWFILE hFile, BOOLEAN fIncludesElgin, BOOLEAN fIncludesManny ) { UINT32 uiNumBytesRead; UINT8 ubArmsDealer; UINT16 usItemIndex; //Free all the dealers special inventory arrays ShutDownArmsDealers(); // Elgin was added to the dealers list in Game Version #54, enlarging these 2 tables... // Manny was added to the dealers list in Game Version #55, enlarging these 2 tables... if ( fIncludesElgin && fIncludesManny ) { // info for all dealers is in the save file //Load the arms dealers status if ( !FileRead( hFile, gArmsDealerStatus, sizeof( gArmsDealerStatus ), &uiNumBytesRead )) { return( FALSE ); } //load the dealers inventory item headers (all at once) if ( !FileRead( hFile, gArmsDealersInventory, sizeof( gArmsDealersInventory ), &uiNumBytesRead )) { return( FALSE ); } } else { if ( !LoadIncompleteArmsDealersStatus( hFile, fIncludesElgin, fIncludesManny ) ) { return( FALSE ); } } //loop through all the dealers inventories for( ubArmsDealer=0; ubArmsDealer 0 ) { //Allocate memory for the inventory if ( !AllocMemsetSpecialItemArray( &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ], gArmsDealersInventory[ubArmsDealer][usItemIndex].ubElementsAlloced )) return(FALSE); if (!FileRead( hFile, &gArmsDealersInventory[ubArmsDealer][usItemIndex].SpecialItem[0], sizeof( DEALER_SPECIAL_ITEM ) * gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced, &uiNumBytesRead )) { return( FALSE ); } } } } return( TRUE ); } void DailyUpdateOfArmsDealersInventory() { // if Gabby has creature blood, start turning it into extra elixir ConvertCreatureBloodToElixir(); //Simulate other customers buying inventory from the dealer SimulateArmsDealerCustomer(); //if there are some items that are out of stock, order some more DailyCheckOnItemQuantities(); //make sure certain items are in stock and certain limits are respected AdjustCertainDealersInventory( ); } // Once a day, loop through each dealer's inventory items and possibly sell some void SimulateArmsDealerCustomer() { UINT8 ubArmsDealer=0; UINT16 usItemIndex; UINT8 ubItemsSold=0; UINT8 ubElement; SPECIAL_ITEM_INFO SpclItemInfo; //loop through all the arms dealers for( ubArmsDealer=0;ubArmsDealer 0) { // first, try to sell all the new (perfect) ones if ( usItemIndex == JAR_ELIXIR ) { // only allow selling of standard # of items so those converted from blood given by player will be available ubItemsSold = HowManyItemsAreSold( ubArmsDealer, usItemIndex, (UINT8) __min( 3, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems), FALSE); } else { ubItemsSold = HowManyItemsAreSold( ubArmsDealer, usItemIndex, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems, FALSE); } if ( ubItemsSold > 0) { // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); //Now remove that many NEW ones (condition 100) of that item RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ubItemsSold); } // next, try to sell all the used ones, gotta do these one at a time so we can remove them by element for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { // don't worry about negative condition, repairmen can't come this far, they don't sell! if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) { // try selling just this one if (HowManyItemsAreSold( ubArmsDealer, usItemIndex, 1, TRUE) > 0) { //Sold, now remove that particular USED one! RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement ); } } } } } } } void DailyCheckOnItemQuantities() { UINT8 ubArmsDealer; UINT16 usItemIndex; UINT8 ubMaxSupply; UINT8 ubNumItems; UINT32 uiArrivalDay; BOOLEAN fPrevElig; UINT8 ubReorderDays; //loop through all the arms dealers for( ubArmsDealer=0;ubArmsDealer= GetWorldDay() ) { ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder); //reset order gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubQtyOnOrder = 0; gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].uiOrderArrivalTime = 0; } } } } } } void ConvertCreatureBloodToElixir( void ) { UINT8 ubBloodAvailable; UINT8 ubAmountToConvert; SPECIAL_ITEM_INFO SpclItemInfo; ubBloodAvailable = gArmsDealersInventory[ ARMS_DEALER_GABBY ][ JAR_CREATURE_BLOOD ].ubTotalItems; if ( ubBloodAvailable ) { // start converting blood into elixir! //ubAmountToConvert = (UINT8) __min( 5 + Random( 3 ), ubBloodAvailable ); ubAmountToConvert = ubBloodAvailable; // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); //Now remove that many NEW ones (condition 100) of that item RemoveItemFromArmsDealerInventory( ARMS_DEALER_GABBY, JAR_CREATURE_BLOOD, &SpclItemInfo, ubAmountToConvert ); ArmsDealerGetsFreshStock( ARMS_DEALER_GABBY, JAR_ELIXIR, ubAmountToConvert ); } } BOOLEAN AdjustCertainDealersInventory( ) { //Adjust Tony's items (this restocks *instantly* 1/day, doesn't use the reorder system) GuaranteeAtLeastOneItemOfType( ARMS_DEALER_TONY, ARMS_DEALER_BIG_GUNS ); LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_BIG_GUNS, 2 ); LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_HANDGUNCLASS, 3 ); LimitArmsDealersInventory( ARMS_DEALER_TONY, ARMS_DEALER_AMMO, 8 ); //Adjust all bartenders' alcohol levels to a minimum GuaranteeMinimumAlcohol( ARMS_DEALER_FRANK ); GuaranteeMinimumAlcohol( ARMS_DEALER_BAR_BRO_1 ); GuaranteeMinimumAlcohol( ARMS_DEALER_BAR_BRO_2 ); GuaranteeMinimumAlcohol( ARMS_DEALER_BAR_BRO_3 ); GuaranteeMinimumAlcohol( ARMS_DEALER_BAR_BRO_4 ); GuaranteeMinimumAlcohol( ARMS_DEALER_ELGIN ); GuaranteeMinimumAlcohol( ARMS_DEALER_MANNY ); //make sure Sam (hardware guy) has at least one empty jar GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_SAM, JAR, 1 ); if ( CheckFact( FACT_ESTONI_REFUELLING_POSSIBLE, 0 ) ) { // gas is restocked regularly, unlike most items GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_JAKE, GAS_CAN, ( UINT8 ) ( 4 + Random( 3 ) ) ); } //If the player hasn't bought a video camera from Franz yet, make sure Franz has one to sell if( !( gArmsDealerStatus[ ARMS_DEALER_FRANZ ].ubSpecificDealerFlags & ARMS_DEALER_FLAG__FRANZ_HAS_SOLD_VIDEO_CAMERA_TO_PLAYER ) ) { GuaranteeAtLeastXItemsOfIndex( ARMS_DEALER_FRANZ, VIDEO_CAMERA, 1 ); } return( TRUE ); } void LimitArmsDealersInventory( UINT8 ubArmsDealer, UINT32 uiDealerItemType, UINT8 ubMaxNumberOfItemType ) { UINT16 usItemIndex=0; UINT32 uiItemsToRemove=0; SPECIAL_ITEM_INFO SpclItemInfo; UINT16 usAvailableItem[ MAXITEMS ] = { NOTHING }; UINT8 ubNumberOfAvailableItem[ MAXITEMS ] = { 0 }; UINT32 uiTotalNumberOfItems = 0, uiRandomChoice; UINT32 uiNumAvailableItems = 0, uiIndex; // not permitted for repair dealers - would take extra code to avoid counting items under repair! Assert( !DoesDealerDoRepairs( ubArmsDealer ) ); if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness ) return; //loop through all items of the same class and count the number in stock for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //if there is some items in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0) { //if the item is of the same dealer item type if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) ) { usAvailableItem[ uiNumAvailableItems ] = usItemIndex; //if the dealer item type is ammo if( uiDealerItemType == ARMS_DEALER_AMMO ) { // all ammo of same type counts as only one item ubNumberOfAvailableItem[ uiNumAvailableItems ] = 1; uiTotalNumberOfItems++; } else { // items being repaired don't count against the limit ubNumberOfAvailableItem[ uiNumAvailableItems ] = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems; uiTotalNumberOfItems += ubNumberOfAvailableItem[ uiNumAvailableItems ]; } uiNumAvailableItems++; } } } //if there is more of the given type than we want if( uiNumAvailableItems > ubMaxNumberOfItemType ) { uiItemsToRemove = uiNumAvailableItems - ubMaxNumberOfItemType; do { uiRandomChoice = Random( uiTotalNumberOfItems ); for ( uiIndex = 0; uiIndex < uiNumAvailableItems; uiIndex++ ) { if ( uiRandomChoice <= ubNumberOfAvailableItem[ uiIndex ] ) { usItemIndex = usAvailableItem[ uiIndex ]; if ( uiDealerItemType == ARMS_DEALER_AMMO ) { // remove all of them, since each ammo item counts as only one "item" here // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); // ammo will always be only condition 100, there's never any in special slots RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ); } else { // pick 1 random one, don't care about its condition RemoveRandomItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, 1 ); } // now remove entry from the array by replacing it with the last and decrementing // the size of the array usAvailableItem[ uiIndex ] = usAvailableItem[ uiNumAvailableItems - 1 ]; ubNumberOfAvailableItem[ uiIndex ] = ubNumberOfAvailableItem[ uiNumAvailableItems - 1 ]; uiNumAvailableItems--; // decrement count of # of items to remove uiItemsToRemove--; break; // and out of 'for' loop } else { // next item! uiRandomChoice -= ubNumberOfAvailableItem[ uiIndex ]; } } /* //loop through all items of the same type for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { //if there are some non-repairing items in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ) { //if the item is of the same dealer item type if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) ) { // a random chance that the item will be removed if( Random( 100 ) < 30 ) { //remove the item //if the dealer item type is ammo if( uiDealerItemType == ARMS_DEALER_AMMO ) { // remove all of them, since each ammo item counts as only one "item" here // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); // ammo will always be only condition 100, there's never any in special slots RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ); } else { // pick 1 random one, don't care about its condition RemoveRandomItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, 1 ); } uiItemsToRemove--; if( uiItemsToRemove == 0) break; } } } } */ } while (uiItemsToRemove > 0); } } void GuaranteeAtLeastOneItemOfType( UINT8 ubArmsDealer, UINT32 uiDealerItemType ) { UINT16 usItemIndex; UINT8 ubChance; BOOLEAN fFoundEligibleItemOfSameType = FALSE; BOOLEAN fItemHasBeenAdded = FALSE; BOOLEAN fFailedOnce = FALSE; UINT16 usAvailableItem[ MAXITEMS ] = { NOTHING }; UINT8 ubChanceForAvailableItem[ MAXITEMS ] = { 0 }; UINT32 uiTotalChances = 0; UINT32 uiNumAvailableItems = 0, uiIndex, uiRandomChoice; // not permitted for repair dealers - would take extra code to avoid counting items under repair! Assert( !DoesDealerDoRepairs( ubArmsDealer ) ); if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness ) return; //loop through all items of the same type for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //if the item is of the same dealer item type if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) ) { //if there are any of these in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 ) { //there is already at least 1 item of that type, return return; } // if he can stock it (it appears in his inventory list) if( GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ) > 0) { // and the stage of the game gives him a chance to have it (assume new) ubChance = ChanceOfItemTransaction( ubArmsDealer, usItemIndex, DEALER_BUYING, FALSE ); if ( ubChance > 0 ) { usAvailableItem[ uiNumAvailableItems ] = usItemIndex; ubChanceForAvailableItem[ uiNumAvailableItems ] = ubChance; uiNumAvailableItems++; uiTotalChances += ubChance; } } } } // if there aren't any such items, the following loop would never finish, so quit before trying it! if (uiNumAvailableItems == 0) { return; } // CJC: randomly pick one of available items by weighted random selection. // randomize number within uiTotalChances and then loop forwards till we find that item uiRandomChoice = Random( uiTotalChances ); for ( uiIndex = 0; uiIndex < uiNumAvailableItems; uiIndex++ ) { if ( uiRandomChoice <= ubChanceForAvailableItem[ uiIndex ] ) { ArmsDealerGetsFreshStock( ubArmsDealer, usAvailableItem[ uiIndex ], 1 ); return; } else { // next item! uiRandomChoice -= ubChanceForAvailableItem[ uiIndex ]; } } // internal logic failure! } void GuaranteeAtLeastXItemsOfIndex( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany ) { // not permitted for repair dealers - would take extra code to avoid counting items under repair! Assert( !DoesDealerDoRepairs( ubArmsDealer ) ); if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness ) return; //if there are any of these in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems >= ubHowMany ) { // have what we need... return; } // if he can stock it (it appears in his inventory list) // RESTRICTION REMOVED: Jake must be able to guarantee GAS even though it's not in his list, it's presence is conditional // if( GetDealersMaxItemAmount( ubArmsDealer, usItemIndex ) > 0) { //add the item ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, (UINT8)( ubHowMany - gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ) ); } } UINT32 GetArmsDealerItemTypeFromItemNumber( UINT16 usItem ) { switch( Item[ usItem ].usItemClass ) { case IC_NONE: return( 0 ); break; case IC_GUN: switch( Weapon[ Item[ usItem ].ubClassIndex ].ubWeaponClass ) { case HANDGUNCLASS: return( ARMS_DEALER_HANDGUNCLASS ); break; case RIFLECLASS: if ( ItemIsARocketRifle( usItem ) ) return( ARMS_DEALER_ROCKET_RIFLE ); else return( ARMS_DEALER_RIFLECLASS ); break; case SHOTGUNCLASS: return( ARMS_DEALER_SHOTGUNCLASS ); break; case SMGCLASS: return( ARMS_DEALER_SMGCLASS ); break; case MGCLASS: return( ARMS_DEALER_MGCLASS ); break; case MONSTERCLASS: return( 0 ); break; case KNIFECLASS: return( ARMS_DEALER_KNIFECLASS ); break; } break; case IC_PUNCH: if (usItem == NOTHING) { return( 0 ); } // else treat as blade case IC_BLADE: case IC_THROWING_KNIFE: return( ARMS_DEALER_BLADE ); break; case IC_LAUNCHER: return( ARMS_DEALER_LAUNCHER ); break; case IC_ARMOUR: return( ARMS_DEALER_ARMOUR ); break; case IC_MEDKIT: return( ARMS_DEALER_MEDKIT ); break; case IC_KIT: return( ARMS_DEALER_KIT ); break; case IC_MISC: { if ( Item [usItem].alcohol ) return( ARMS_DEALER_ALCOHOL ); else if ( Item [usItem].electronic ) return( ARMS_DEALER_ELECTRONICS ); else if ( Item [usItem].hardware ) return( ARMS_DEALER_HARDWARE ); else if ( Item [usItem].medical ) return( ARMS_DEALER_MEDICAL ); else if ( Item [usItem].attachment ) return( ARMS_DEALER_ATTACHMENTS ); else if ( Item [usItem].detonator || Item [usItem].remotedetonator || Item [usItem].remotetrigger ) return( ARMS_DEALER_DETONATORS ); else return( ARMS_DEALER_MISC ); //switch on the type of item // switch( usItem ) // { // case BEER: // case WINE: // case ALCOHOL: // return( ARMS_DEALER_ALCOHOL ); // break; // // case METALDETECTOR: // case LASERSCOPE: //// case REMDETONATOR: // return( ARMS_DEALER_ELECTRONICS ); // break; // // case CANTEEN: // case CROWBAR: // case WIRECUTTERS: // return( ARMS_DEALER_HARDWARE ); // break; // // case ADRENALINE_BOOSTER: // case REGEN_BOOSTER: // case SYRINGE_3: // case SYRINGE_4: // case SYRINGE_5: // return( ARMS_DEALER_MEDICAL ); // break; // // case SILENCER: // case SNIPERSCOPE: // case BIPOD: // case DUCKBILL: // return( ARMS_DEALER_ATTACHMENTS ); // break; // // case DETONATOR: // case REMDETONATOR: // case REMOTEBOMBTRIGGER: // return( ARMS_DEALER_DETONATORS ); // break; // // default: // return( ARMS_DEALER_MISC ); // // break; // } } break; case IC_AMMO: return( ARMS_DEALER_AMMO ); break; case IC_FACE: if (Item[usItem].electronic ) return ARMS_DEALER_ELECTRONICS; else return ARMS_DEALER_FACE; //switch( usItem ) //{ // case EXTENDEDEAR: // case NIGHTGOGGLES: // case ROBOT_REMOTE_CONTROL: // return( ARMS_DEALER_ELECTRONICS ); // break; // default: // return( ARMS_DEALER_FACE ); //} break; case IC_THROWN: return( 0 ); // return( ARMS_DEALER_THROWN ); break; case IC_KEY: return( 0 ); // return( ARMS_DEALER_KEY ); break; case IC_GRENADE: return( ARMS_DEALER_GRENADE ); break; case IC_BOMB: return( ARMS_DEALER_BOMB ); break; case IC_EXPLOSV: return( ARMS_DEALER_EXPLOSV ); break; case IC_TENTACLES: case IC_MONEY: return( 0 ); break; // case IC_APPLIABLE: break; default: AssertMsg( FALSE, String( "GetArmsDealerItemTypeFromItemNumber(), invalid class %d for item %d. DF 0.", Item[ usItem ].usItemClass, usItem ) ); break; } return( 0 ); } BOOLEAN IsMercADealer( UINT8 ubMercID ) { UINT8 cnt; // Manny is not actually a valid dealer unless a particular event sets that fact if( ( ubMercID == MANNY ) && !CheckFact( FACT_MANNY_IS_BARTENDER, 0 ) ) { return( FALSE ); } //loop through the list of arms dealers for( cnt=0; cnt 0 ); } //else selling inventory uses their inventory list break; default: // the others go by their inventory list break; } break; case ARMS_DEALER_REPAIRS: // repairmen don't have a complete list of what they'll repair in their inventory, // so we must check the item's properties instead. return( CanDealerRepairItem( ubArmsDealer, usItemIndex ) ); default: AssertMsg( FALSE, String( "CanDealerTransactItem(), type of dealer %d. AM 0.", ArmsDealerInfo[ ubArmsDealer ].ubTypeOfArmsDealer ) ); return(FALSE); } return( DoesItemAppearInDealerInventoryList( ubArmsDealer, usItemIndex, fPurchaseFromPlayer ) ); } BOOLEAN CanDealerRepairItem( UINT8 ubArmsDealer, UINT16 usItemIndex ) { // UINT32 uiFlags; // uiFlags = Item[ usItemIndex ].fFlags; // can't repair anything that's not repairable! // if ( !( uiFlags & ITEM_REPAIRABLE ) ) if ( !( Item[ usItemIndex ].repairable ) ) { return(FALSE); } switch ( ubArmsDealer ) { case ARMS_DEALER_ARNIE: case ARMS_DEALER_PERKO: // repairs ANYTHING non-electronic // if ( !( uiFlags & ITEM_ELECTRONIC ) ) if ( !( Item[ usItemIndex ].electronic ) ) { return(TRUE); } break; case ARMS_DEALER_FREDO: // repairs ONLY electronics // if ( uiFlags & ITEM_ELECTRONIC ) if ( Item[ usItemIndex ].electronic ) { return(TRUE); } break; default: AssertMsg( FALSE, String( "CanDealerRepairItem(), Arms Dealer %d is not a recognized repairman!. AM 1.", ubArmsDealer ) ); } // can't repair this... return(FALSE); } BOOLEAN AllocMemsetSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem, UINT8 ubElementsNeeded ) { Assert(pDealerItem); Assert( ubElementsNeeded > 0); pDealerItem->SpecialItem = (DEALER_SPECIAL_ITEM *) MemAlloc( sizeof( DEALER_SPECIAL_ITEM ) * ubElementsNeeded ); if( pDealerItem->SpecialItem == NULL ) { Assert( 0 ); return(FALSE); } // zero them out (they're inactive until an item is actually added) memset( pDealerItem->SpecialItem, 0, sizeof( DEALER_SPECIAL_ITEM ) * ubElementsNeeded ); pDealerItem->ubElementsAlloced = ubElementsNeeded; return(TRUE); } BOOLEAN ResizeSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem, UINT8 ubElementsNeeded ) { Assert(pDealerItem); // must already have a ptr allocated! Assert(pDealerItem->SpecialItem); if ( ubElementsNeeded == pDealerItem->ubElementsAlloced) { // shouldn't have been called, but what they hey, it's not exactly a problem return(TRUE); } // already allocated, but change its size pDealerItem->SpecialItem = (DEALER_SPECIAL_ITEM *) MemRealloc( pDealerItem->SpecialItem, sizeof( DEALER_SPECIAL_ITEM ) * ubElementsNeeded ); if( pDealerItem->SpecialItem == NULL ) { Assert( 0 ); return(FALSE); } // if adding more elements if ( ubElementsNeeded > pDealerItem->ubElementsAlloced) { // zero them out (they're inactive until an item is actually added) memset( &(pDealerItem->SpecialItem[pDealerItem->ubElementsAlloced]), 0, sizeof( DEALER_SPECIAL_ITEM ) * ( ubElementsNeeded - pDealerItem->ubElementsAlloced) ); } pDealerItem->ubElementsAlloced = ubElementsNeeded; return(TRUE); } void FreeSpecialItemArray( DEALER_ITEM_HEADER *pDealerItem) { Assert(pDealerItem); // must already have a ptr allocated! Assert(pDealerItem->SpecialItem); MemFree( pDealerItem->SpecialItem ); pDealerItem->SpecialItem = NULL; pDealerItem->ubElementsAlloced = 0; pDealerItem->ubTotalItems = pDealerItem->ubPerfectItems; // doesn't effect perfect items, orders or stray bullets! } void ArmsDealerGetsFreshStock( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubNumItems ) { UINT8 ubCnt; UINT8 ubItemCondition; UINT8 ubPerfectOnes = 0; SPECIAL_ITEM_INFO SpclItemInfo; // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); // determine the condition of each one, counting up new ones, but adding damaged ones right away for ( ubCnt = 0; ubCnt < ubNumItems; ubCnt++ ) { ubItemCondition = DetermineDealerItemCondition( ubArmsDealer, usItemIndex); // if the item is brand new if ( ubItemCondition == 100) { ubPerfectOnes++; } else { // add a used item with that condition to his inventory SpclItemInfo.bItemCondition = (INT8) ubItemCondition; AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, 1 ); } } // now add all the perfect ones, in one shot if ( ubPerfectOnes > 0) { SpclItemInfo.bItemCondition = 100; AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ubPerfectOnes ); } } UINT8 DetermineDealerItemCondition( UINT8 ubArmsDealer, UINT16 usItemIndex ) { UINT8 ubCondition = 100; // if it's a damagable item, and not a liquid (those are always sold full) // if ( ( Item[ usItemIndex ].fFlags & ITEM_DAMAGEABLE ) && !ItemContainsLiquid( usItemIndex ) ) if ( ( Item[ usItemIndex ].damageable ) && !ItemContainsLiquid( usItemIndex ) ) { // if he ONLY has used items, or 50% of the time if he carries both used & new items if ( ( ArmsDealerInfo[ ubArmsDealer ].uiFlags & ARMS_DEALER_ONLY_USED_ITEMS ) || ( ( ArmsDealerInfo[ ubArmsDealer ].uiFlags & ARMS_DEALER_SOME_USED_ITEMS ) && ( Random( 100 ) < 50 ) ) ) { // make the item a used one ubCondition = (UINT8)(20 + Random( 60 )); } } return( ubCondition); } BOOLEAN ItemContainsLiquid( UINT16 usItemIndex ) { if(Item[usItemIndex].containsliquid) return TRUE; else return FALSE; //switch ( usItemIndex ) //{ // case CANTEEN: // case BEER: // case ALCOHOL: // case JAR_HUMAN_BLOOD: // case JAR_CREATURE_BLOOD: // case JAR_QUEEN_CREATURE_BLOOD: // case JAR_ELIXIR: // case GAS_CAN: // return( TRUE ); //} //return( FALSE ); } /* UINT32 CountTotalItemsInArmsDealersInventory( UINT8 ubArmsDealer ) { UINT32 uiNumOfItems=0; UINT16 usItemIndex; //loop through all the items in this dealer's inventory for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { // This counts each pack of ammo or stacked item as one. See the "distinct" version of this for an alternate version uiNumOfItems += gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems; } return( uiNumOfItems ); } */ UINT32 CountDistinctItemsInArmsDealersInventory( UINT8 ubArmsDealer ) { UINT32 uiNumOfItems=0; UINT16 usItemIndex; for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //if there are any items if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 ) { // if there are any items in perfect condition if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems > 0 ) { // if the items can be stacked // NOTE: This test must match the one inside AddItemsToTempDealerInventory() exactly! if ( DealerItemIsSafeToStack( usItemIndex ) ) { // regardless of how many there are, they count as 1 *distinct* item! They will all be together in one box... uiNumOfItems++; } else { // non-stacking items must be stored in one / box , because each may have unique fields besides bStatus[] // Example: guns all have ammo, ammo type, etc. We need these uniquely represented for pricing & manipulation uiNumOfItems += gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems; } } // each *active* special item counts as one additional distinct item (each one occupied a separate shopkeeper box!) // NOTE: This is including items being repaired!!! uiNumOfItems += CountActiveSpecialItemsInArmsDealersInventory( ubArmsDealer, usItemIndex); } } return( uiNumOfItems ); } UINT8 CountActiveSpecialItemsInArmsDealersInventory( UINT8 ubArmsDealer, UINT16 usItemIndex ) { UINT8 ubActiveSpecialItems = 0; UINT8 ubElement; // next, try to sell all the used ones, gotta do these one at a time so we can remove them by element for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { // don't worry about negative condition, repairmen can't come this far, they don't sell! if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) { ubActiveSpecialItems++; } } return( ubActiveSpecialItems ); } UINT16 CountTotalItemsRepairDealerHasInForRepairs( UINT8 ubArmsDealer ) { UINT16 usItemIndex; UINT16 usHowManyInForRepairs = 0; //if the dealer is not a repair dealer, no need to count, return 0 if( !DoesDealerDoRepairs( ubArmsDealer ) ) return( 0 ); //loop through the dealers inventory and count the number of items in for repairs for( usItemIndex=0; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; usHowManyInForRepairs += CountSpecificItemsRepairDealerHasInForRepairs( ubArmsDealer, usItemIndex ); } return( usHowManyInForRepairs ); } UINT8 CountSpecificItemsRepairDealerHasInForRepairs( UINT8 ubArmsDealer, UINT16 usItemIndex ) { UINT8 ubElement; UINT8 ubHowManyInForRepairs = 0; //if the dealer is not a repair dealer, no need to count, return 0 if( !DoesDealerDoRepairs( ubArmsDealer ) ) return( 0 ); //if there is some items in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ) { //loop through the array of items for( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive) { //if the item's status is below 0, the item is being repaired if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 ) { ubHowManyInForRepairs++; } } } } return( ubHowManyInForRepairs ); } void AddObjectToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE *pObject ) { UINT8 ubCnt; SPECIAL_ITEM_INFO SpclItemInfo; SetSpecialItemInfoFromObject( &SpclItemInfo, pObject ); // split up all the components of an objecttype and add them as seperate items into the dealer's inventory switch ( Item [ pObject->usItem ].usItemClass ) { case IC_GUN: // add the gun (keeps the object's status and imprintID) // if the gun was jammed, this will forget about the jam (i.e. dealer immediately unjams anything he buys) AddItemToArmsDealerInventory( ubArmsDealer, pObject->usItem, &SpclItemInfo, 1 ); // if any GunAmmoItem is specified if( pObject->usGunAmmoItem != NONE) { // if it's regular ammo if( Item[ pObject->usGunAmmoItem ].usItemClass == IC_AMMO ) { // and there are some remaining if ( pObject->ubGunShotsLeft > 0 ) { // add the bullets of its remaining ammo AddAmmoToArmsDealerInventory( ubArmsDealer, pObject->usGunAmmoItem, pObject->ubGunShotsLeft ); } } else // assume it's attached ammo (mortar shells, grenades) { // add the launchable item (can't be imprinted, or have attachments!) SetSpecialItemInfoToDefaults( &SpclItemInfo ); SpclItemInfo.bItemCondition = pObject->bGunAmmoStatus; // if the gun it was in was jammed, get rid of the negative status now if ( SpclItemInfo.bItemCondition < 0 ) { SpclItemInfo.bItemCondition *= -1; } AddItemToArmsDealerInventory( ubArmsDealer, pObject->usGunAmmoItem, &SpclItemInfo, 1 ); } } break; case IC_AMMO: // add the contents of each magazine (multiple mags may have vastly different #bullets left) for ( ubCnt = 0; ubCnt < pObject->ubNumberOfObjects; ubCnt++ ) { AddAmmoToArmsDealerInventory( ubArmsDealer, pObject->usItem, pObject->ubShotsLeft[ ubCnt ] ); } break; default: // add each object seperately (multiple objects may have vastly different statuses, keep any imprintID) for ( ubCnt = 0; ubCnt < pObject->ubNumberOfObjects; ubCnt++ ) { SpclItemInfo.bItemCondition = pObject->bStatus[ ubCnt ]; AddItemToArmsDealerInventory( ubArmsDealer, pObject->usItem, &SpclItemInfo, 1 ); } break; } // loop through any detachable attachments and add them as seperate items for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if( pObject->usAttachItem[ ubCnt ] != NONE ) { // ARM: Note: this is only used for selling, not repairs, so attachmentes are seperated when sold to a dealer // If the attachment is detachable // if (! (Item[ pObject->usAttachItem[ubCnt] ].fFlags & ITEM_INSEPARABLE ) ) if (! (Item[ pObject->usAttachItem[ubCnt] ].inseparable ) ) { // add this particular attachment (they can't be imprinted, or themselves have attachments!) SetSpecialItemInfoToDefaults( &SpclItemInfo ); SpclItemInfo.bItemCondition = pObject->bAttachStatus[ ubCnt ]; AddItemToArmsDealerInventory( ubArmsDealer, pObject->usAttachItem[ ubCnt ], &SpclItemInfo, 1 ); } } } // nuke the original object to prevent any possible item duplication memset( pObject, 0, sizeof( OBJECTTYPE ) ); } void AddAmmoToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubShotsLeft ) { UINT8 ubMagCapacity; UINT8 *pubStrayAmmo; SPECIAL_ITEM_INFO SpclItemInfo; // Ammo only, please!!! if (Item [ usItemIndex ].usItemClass != IC_AMMO ) { Assert(0); return; } if ( ubShotsLeft == 0) { return; } ubMagCapacity = Magazine[ Item[ usItemIndex ].ubClassIndex ].ubMagSize; if ( ubShotsLeft >= ubMagCapacity ) { // add however many FULL magazines the #shot left represents SetSpecialItemInfoToDefaults( &SpclItemInfo ); AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ( UINT8 ) ( ubShotsLeft / ubMagCapacity ) ); ubShotsLeft %= ubMagCapacity; } // any shots left now are "strays" - not enough to completely fill a magazine of this type if ( ubShotsLeft > 0 ) { // handle "stray" ammo - add it to the dealer's stray pile pubStrayAmmo = &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubStrayAmmo); *pubStrayAmmo += ubShotsLeft; // if dealer has accumulated enough stray ammo to make another full magazine, convert it! if ( *pubStrayAmmo >= ubMagCapacity ) { SetSpecialItemInfoToDefaults( &SpclItemInfo ); AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, ( UINT8 ) ( *pubStrayAmmo / ubMagCapacity ) ); *pubStrayAmmo = *pubStrayAmmo % ubMagCapacity; } // I know, I know, this is getting pretty anal... But what the hell, it was easy enough to do. ARM. } } //Use AddObjectToArmsDealerInventory() instead of this when converting a complex item in OBJECTTYPE format. void AddItemToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, UINT8 ubHowMany ) { UINT8 ubRoomLeft; UINT8 ubElement; UINT8 ubElementsToAdd; BOOLEAN fFoundOne; BOOLEAN fSuccess; Assert( ubHowMany > 0); ubRoomLeft = 255 - gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems; if ( ubHowMany > ubRoomLeft) { // not enough room to store that many, any extras vanish into thin air! ubHowMany = ubRoomLeft; } if ( ubHowMany == 0) { return; } // decide whether this item is "special" or not if ( IsItemInfoSpecial( pSpclItemInfo ) ) { // Anything that's used/damaged or imprinted is store as a special item in the SpecialItem array, // exactly one item per element. We (re)allocate memory dynamically as necessary to hold the additional items. do { // search for an already allocated, empty element in the special item array fFoundOne = FALSE; for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { if ( !( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) ) { //Great! Store it here, then. AddSpecialItemToArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement, pSpclItemInfo ); fFoundOne = TRUE; break; } } // if we didn't find any inactive elements already allocated if (!fFoundOne) { // then we're going to have to allocate some more space... ubElementsToAdd = max( SPECIAL_ITEMS_ALLOCED_AT_ONCE, ubHowMany); // if there aren't any allocated at all right now if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced == 0 ) { // allocate new memory for the real buffer fSuccess = AllocMemsetSpecialItemArray( &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ], ubElementsToAdd ); } else { // we have some allocated, but they're all full and we need more. MemRealloc existing amount + # addition elements fSuccess = ResizeSpecialItemArray( &gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ], (UINT8)( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced + ubElementsToAdd ) ); } if ( !fSuccess ) { return; } // now add the special item at the first of the newly added elements (still stored in ubElement!) AddSpecialItemToArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement, pSpclItemInfo ); } // store the # of the element it was placed in globally so anyone who needs that can grab it there gubLastSpecialItemAddedAtElement = ubElement; ubHowMany--; } while ( ubHowMany > 0); } else // adding perfect item(s) { // then it's stored as a "perfect" item, simply add it to that counter! gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems += ubHowMany; // increase total items of this type gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems += ubHowMany; } } void AddSpecialItemToArmsDealerInventoryAtElement( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubElement, SPECIAL_ITEM_INFO *pSpclItemInfo ) { Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems < 255 ); Assert( ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced ); Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive == FALSE ); Assert( IsItemInfoSpecial( pSpclItemInfo ) ); //Store the special values in that element, and make it active gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive = TRUE; memcpy( &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info), pSpclItemInfo, sizeof( SPECIAL_ITEM_INFO ) ); // increase the total items gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems++; } // removes ubHowMany items of usItemIndex with the matching Info from dealer ubArmsDealer void RemoveItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, UINT8 ubHowMany ) { DEALER_SPECIAL_ITEM *pSpecialItem; UINT8 ubElement; Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ); if ( ubHowMany == 0) { return; } // decide whether this item is "special" or not if ( IsItemInfoSpecial( pSpclItemInfo ) ) { // look through the elements, trying to find special items matching the specifications for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { pSpecialItem = &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ]); // if this element is in use if ( pSpecialItem->fActive ) { // and its contents are exactly what we're looking for if( memcmp( &(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info), pSpclItemInfo, sizeof( SPECIAL_ITEM_INFO ) ) == 0 ) { // Got one! Remove it RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement ); ubHowMany--; if ( ubHowMany == 0) { break; } } } } // when we've searched all the special item elements, we'd better not have any more items to remove! Assert( ubHowMany == 0); } else // removing perfect item(s) { // then it's stored as a "perfect" item, simply subtract from tha counter! Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems ); gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems -= ubHowMany; // decrease total items of this type gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems -= ubHowMany; } } void RemoveRandomItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany ) { UINT8 ubWhichOne; UINT8 ubSkippedAlready; BOOLEAN fFoundIt; UINT8 ubElement; SPECIAL_ITEM_INFO SpclItemInfo; // not permitted for repair dealers - would take extra code to subtract items under repair from ubTotalItems!!! Assert( !DoesDealerDoRepairs( ubArmsDealer ) ); // Can't remove any items in for repair, though! Assert( ubHowMany <= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ); while ( ubHowMany > 0) { // pick a random one to get rid of ubWhichOne = (UINT8)Random(gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems ); // if we picked one of the perfect ones... if ( ubWhichOne < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems ) { // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); // then that's easy, its condition is 100, so remove one of those RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, &SpclItemInfo, 1 ); } else { // Yikes! Gotta look through the special items. We already know it's not any of the perfect ones, subtract those ubWhichOne -= gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubPerfectItems; ubSkippedAlready = 0; fFoundIt = FALSE; for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { // if this is an active special item, not in repair if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) // && // ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition > 0 ) ) { // if we skipped the right amount of them if ( ubSkippedAlready == ubWhichOne ) { // then this one is it! That's the one we're gonna remove RemoveSpecialItemFromArmsDealerInventoryAtElement( ubArmsDealer, usItemIndex, ubElement ); fFoundIt = TRUE; break; } else { // keep looking... ubSkippedAlready++; } } } // this HAS to work, or the data structure is corrupt! Assert(fFoundIt); } ubHowMany--; } } void RemoveSpecialItemFromArmsDealerInventoryAtElement( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubElement ) { Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 ); Assert( ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced ); Assert( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive == TRUE ); // wipe it out (turning off fActive) memset( &( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ] ), 0, sizeof( DEALER_SPECIAL_ITEM ) ); // one fewer item remains... gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems--; } BOOLEAN AddDeadArmsDealerItemsToWorld( UINT8 ubMercID ) { INT8 bArmsDealer; SOLDIERTYPE *pSoldier; UINT16 usItemIndex; UINT8 ubElement; UINT8 ubHowManyMaxAtATime; UINT8 ubLeftToDrop; UINT8 ubNowDropping; OBJECTTYPE TempObject; DEALER_SPECIAL_ITEM *pSpecialItem; SPECIAL_ITEM_INFO SpclItemInfo; //Get Dealer ID from from merc Id bArmsDealer = GetArmsDealerIDFromMercID( ubMercID ); if( bArmsDealer == -1 ) { // not a dealer, that's ok, we get called for every dude that croaks. return( FALSE ); } // mark the dealer as being out of business! gArmsDealerStatus[ bArmsDealer ].fOutOfBusiness = TRUE; //Get a pointer to the dealer pSoldier = FindSoldierByProfileID( ubMercID, FALSE ); if( pSoldier == NULL ) { // This should never happen, a dealer getting knocked off without the sector being loaded, should it? // If it's possible, we should modify code below to dump his belongings into the sector without using pSoldier->sGridNo Assert(0); return( FALSE ); } //loop through all the items in the dealer's inventory, and drop them all where the dealer was set up. for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //if the dealer has any items of this type if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubTotalItems > 0) { // if he has any perfect items of this time if ( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems > 0 ) { // drop all the perfect items first // drop stackable items like ammo in stacks of whatever will fit into a large pocket instead of one at a time ubHowManyMaxAtATime = ItemSlotLimit( usItemIndex, BIGPOCK1POS ); if ( ubHowManyMaxAtATime < 1 ) { ubHowManyMaxAtATime = 1; } // create item info describing a perfect item SetSpecialItemInfoToDefaults( &SpclItemInfo ); ubLeftToDrop = gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems; // ATE: While it IS leagal here to use pSoldier->sInitialGridNo, cause of where this // function is called, there are times when we're not guarenteed that sGridNo is good while ( ubLeftToDrop > 0) { ubNowDropping = min( ubLeftToDrop, ubHowManyMaxAtATime ); MakeObjectOutOfDealerItems( usItemIndex, &SpclItemInfo, &TempObject, ubNowDropping ); AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 ); ubLeftToDrop -= ubNowDropping; } // remove them all from his inventory RemoveItemFromArmsDealerInventory( bArmsDealer, usItemIndex, &SpclItemInfo, gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubPerfectItems ); } // then drop all the special items for ( ubElement = 0; ubElement < gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { pSpecialItem = &(gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ]); if ( pSpecialItem->fActive ) { MakeObjectOutOfDealerItems(usItemIndex, &(pSpecialItem->Info), &TempObject, 1 ); AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 ); RemoveItemFromArmsDealerInventory( bArmsDealer, usItemIndex, &(pSpecialItem->Info), 1 ); } } // release any memory allocated for special items, he won't need it now... if( gArmsDealersInventory[ bArmsDealer ][ usItemIndex ].ubElementsAlloced > 0 ) { FreeSpecialItemArray( &gArmsDealersInventory[ bArmsDealer ][ usItemIndex ] ); } } } //if the dealer has money if( gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash > 0 ) { //Create the object memset( &TempObject, 0, sizeof( OBJECTTYPE ) ); if( !CreateMoney( gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash, &TempObject ) ) { return( FALSE ); } //add the money item to the dealers feet AddItemToPool( pSoldier->sInitialGridNo, &TempObject, INVISIBLE, 0, 0, 0 ); gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash = 0; } return( TRUE ); } void MakeObjectOutOfDealerItems( UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, OBJECTTYPE *pObject, UINT8 ubHowMany ) { INT8 bItemCondition; UINT8 ubCnt; bItemCondition = pSpclItemInfo->bItemCondition; //if the item condition is below 0, the item is in for repairs, so flip the sign if( bItemCondition < 0 ) { bItemCondition *= -1; } memset( pObject, 0, sizeof( OBJECTTYPE ) ); //Create the item object CreateItems( usItemIndex, bItemCondition, ubHowMany, pObject ); // set the ImprintID pObject->ubImprintID = pSpclItemInfo->ubImprintID; // add any attachments we've been storing for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE ) { // store what it is, and its condition pObject->usAttachItem[ ubCnt ] = pSpclItemInfo->usAttachment[ ubCnt ]; pObject->bAttachStatus[ ubCnt ] = pSpclItemInfo->bAttachmentStatus[ ubCnt ]; } } // if it's a gun if (Item [ pObject->usItem ].usItemClass == IC_GUN ) { // Empty out the bullets put in by CreateItem(). We now sell all guns empty of bullets. This is so that we don't // have to keep track of #bullets in a gun throughout dealer inventory. Without this, players could "reload" guns // they don't have ammo for by selling them to Tony & buying them right back fully loaded! One could repeat this // ad nauseum (empty the gun between visits) as a (really expensive) way to get unlimited special ammo like rockets. pObject->ubGunShotsLeft = 0; } } void GiveObjectToArmsDealerForRepair( UINT8 ubArmsDealer, OBJECTTYPE *pObject, UINT8 ubOwnerProfileId ) { // UINT8 ubCnt; SPECIAL_ITEM_INFO SpclItemInfo; Assert( DoesDealerDoRepairs( ubArmsDealer ) ); // Any object passed into here must already be: // a) Unstacked Assert( pObject->ubNumberOfObjects == 1 ); // b) Repairable Assert( CanDealerRepairItem( ubArmsDealer, pObject->usItem ) ); // c) Actually damaged, or a rocket rifle (being reset) Assert( ( pObject->bStatus[ 0 ] < 100 ) || ItemIsARocketRifle( pObject->usItem ) ); /* ARM: Can now repair with removeable attachments still attached... // d) Already stripped of all *detachable* attachments for( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pObject->usAttachItem[ ubCnt ] != NONE ) { // If the attachment is detachable if (! (Item[ pObject->usAttachItem[ubCnt] ].fFlags & ITEM_INSEPARABLE ) ) { Assert( 0 ); } } } */ // e) If a gun, stripped of any non-ammo-class GunAmmoItems, and bullets if (Item [ pObject->usItem ].usItemClass == IC_GUN ) { // if any GunAmmoItem is specified if( pObject->usGunAmmoItem != NONE) { // it better be regular ammo, and empty Assert( Item[ pObject->usGunAmmoItem ].usItemClass == IC_AMMO ); Assert( pObject->ubGunShotsLeft == 0 ); } } SetSpecialItemInfoFromObject( &SpclItemInfo, pObject ); // ok, given all that, now everything is easy! // if the gun was jammed, this will forget about the jam (i.e. dealer immediately unjams anything he will be repairing) GiveItemToArmsDealerforRepair( ubArmsDealer, pObject->usItem, &SpclItemInfo, ubOwnerProfileId ); } //PLEASE: Use GiveObjectToArmsDealerForRepair() instead of this when repairing a item in OBJECTTYPE format. void GiveItemToArmsDealerforRepair( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo, UINT8 ubOwnerProfileId ) { UINT32 uiTimeWhenFreeToStartIt; UINT32 uiMinutesToFix; UINT32 uiMinutesShopClosedBeforeItsDone; UINT32 uiDoneWhen; Assert( DoesDealerDoRepairs( ubArmsDealer ) ); Assert( pSpclItemInfo->bItemCondition > 0 ); Assert( ( pSpclItemInfo->bItemCondition < 100 ) || ItemIsARocketRifle( usItemIndex ) ); // figure out the earliest the repairman will be free to start repairing this item uiTimeWhenFreeToStartIt = WhenWillRepairmanBeAllDoneRepairing( ubArmsDealer ); //Determine how long it will take to fix uiMinutesToFix = CalculateSpecialItemRepairTime( ubArmsDealer, usItemIndex, pSpclItemInfo ); uiMinutesShopClosedBeforeItsDone = CalculateOvernightRepairDelay( ubArmsDealer, uiTimeWhenFreeToStartIt, uiMinutesToFix ); // clock time when this will finally be ready uiDoneWhen = uiTimeWhenFreeToStartIt + uiMinutesToFix + uiMinutesShopClosedBeforeItsDone; // Negate the status pSpclItemInfo->bItemCondition *= -1; // give it to the dealer AddItemToArmsDealerInventory( ubArmsDealer, usItemIndex, pSpclItemInfo, 1 ); //Set the time at which item will be fixed gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ gubLastSpecialItemAddedAtElement ].uiRepairDoneTime = uiDoneWhen; //Remember the original owner of the item gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ gubLastSpecialItemAddedAtElement ].ubOwnerProfileId = ubOwnerProfileId; } UINT32 WhenWillRepairmanBeAllDoneRepairing( UINT8 ubArmsDealer ) { UINT32 uiWhenFree; UINT16 usItemIndex; UINT8 ubElement; Assert( DoesDealerDoRepairs( ubArmsDealer ) ); // if nothing is in for repairs, he'll be free RIGHT NOW! uiWhenFree = GetWorldTotalMin(); //loop through the dealers inventory for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ ) { if ( Item[usItemIndex].usItemClass == 0 ) break; //if there is some items in stock if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems > 0 ) { for ( ubElement = 0; ubElement < gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubElementsAlloced; ubElement++ ) { if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].fActive ) { //if the item is in for repairs if( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].Info.bItemCondition < 0 ) { // if this item will be done later than the latest we've found so far if ( gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime > uiWhenFree ) { // then we're busy til then! uiWhenFree = gArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].SpecialItem[ ubElement ].uiRepairDoneTime; } } } } } } return( uiWhenFree ); } UINT32 CalculateSpecialItemRepairTime( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo ) { UINT32 uiRepairTime; UINT8 ubCnt; uiRepairTime = CalculateSimpleItemRepairTime( ubArmsDealer, usItemIndex, pSpclItemInfo->bItemCondition ); // add time to repair any attachments on it for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE ) { // if damaged and repairable if ( ( pSpclItemInfo->bAttachmentStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ] ) ) { uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ], pSpclItemInfo->bAttachmentStatus[ ubCnt ] ); } } } return( uiRepairTime ); } UINT32 CalculateObjectItemRepairTime( UINT8 ubArmsDealer, OBJECTTYPE *pItemObject ) { UINT32 uiRepairTime; UINT8 ubCnt; uiRepairTime = CalculateSimpleItemRepairTime( ubArmsDealer, pItemObject->usItem, pItemObject->bStatus[ 0 ] ); // add time to repair any attachments on it for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pItemObject->usAttachItem[ ubCnt ] != NONE ) { // if damaged and repairable if ( ( pItemObject->bAttachStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ] ) ) { uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ], pItemObject->bAttachStatus[ ubCnt ] ); } } } return( uiRepairTime ); } UINT32 CalculateSimpleItemRepairTime( UINT8 ubArmsDealer, UINT16 usItemIndex, INT8 bItemCondition ) { UINT32 uiTimeToRepair = 0; UINT32 uiRepairCost = 0; Assert( DoesDealerDoRepairs( ubArmsDealer ) ); // first calc what he'll charge - that takes care of item condition, repair ease, and his repair cost "markup" uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, usItemIndex, bItemCondition ); // Now adjust that for the repairman's individual repair speed. // For a repairman, his BUY modifier controls his REPAIR SPEED (1.0 means minutes to repair = price in $) // with a REPAIR SPEED of 1.0, typical gun price of $2000, and a REPAIR COST of 0.5 this works out to 16.6 hrs // for a full 100% status repair... Not bad. uiTimeToRepair = (UINT32)( uiRepairCost * ArmsDealerInfo[ ubArmsDealer ].dRepairSpeed ); // repairs on electronic items take twice as long if the guy doesn't have the skill // for dealers, this means anyone but Fredo the Electronics guy takes twice as long (but doesn't charge double) // (Mind you, current he's the ONLY one who CAN repair Electronics at all! Oh well.) // if( ( Item[ usItemIndex ].fFlags & ITEM_ELECTRONIC ) && ( ubArmsDealer != ARMS_DEALER_FREDO ) ) if( ( Item[ usItemIndex ].electronic ) && ( ubArmsDealer != ARMS_DEALER_FREDO ) ) { uiTimeToRepair *= 2; } // avoid "instant" repairs on really cheap, barely damaged crap... if (uiTimeToRepair < MIN_REPAIR_TIME_IN_MINUTES) { uiTimeToRepair = MIN_REPAIR_TIME_IN_MINUTES; } return( uiTimeToRepair ); } UINT32 CalculateSpecialItemRepairCost( UINT8 ubArmsDealer, UINT16 usItemIndex, SPECIAL_ITEM_INFO *pSpclItemInfo ) { UINT32 uiRepairCost; UINT8 ubCnt; uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, usItemIndex, pSpclItemInfo->bItemCondition ); // add cost of repairing any attachments on it for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE ) { // if damaged and repairable if ( ( pSpclItemInfo->bAttachmentStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ] ) ) { uiRepairCost += CalculateSimpleItemRepairCost( ubArmsDealer, pSpclItemInfo->usAttachment[ ubCnt ], pSpclItemInfo->bAttachmentStatus[ ubCnt ] ); } } } return( uiRepairCost ); } UINT32 CalculateObjectItemRepairCost( UINT8 ubArmsDealer, OBJECTTYPE *pItemObject ) { UINT32 uiRepairCost; UINT8 ubCnt; uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, pItemObject->usItem, pItemObject->bStatus[ 0 ] ); // add cost of repairing any attachments on it for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pItemObject->usAttachItem[ ubCnt ] != NONE ) { // if damaged and repairable if ( ( pItemObject->bAttachStatus[ ubCnt ] < 100 ) && CanDealerRepairItem( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ] ) ) { uiRepairCost += CalculateSimpleItemRepairCost( ubArmsDealer, pItemObject->usAttachItem[ ubCnt ], pItemObject->bAttachStatus[ ubCnt ] ); } } } return( uiRepairCost ); } UINT32 CalculateSimpleItemRepairCost( UINT8 ubArmsDealer, UINT16 usItemIndex, INT8 bItemCondition ) { UINT32 uiItemCost = 0; UINT32 uiRepairCost = 0; INT16 sRepairCostAdj = 0; // UINT32 uiDifFrom10=0; // figure out the full value of the item, modified by this dealer's personal Sell (i.e. repair cost) modifier // don't use CalcShopKeeperItemPrice - we want FULL value!!! uiItemCost = (UINT32)(( Item[ usItemIndex ].usPrice * ArmsDealerInfo[ ubArmsDealer ].dRepairCost ) ); // get item's repair ease, for each + point is 10% easier, each - point is 10% harder to repair sRepairCostAdj = 100 - ( 10 * Item[ usItemIndex ].bRepairEase ); // make sure it ain't somehow gone too low! if (sRepairCostAdj < 10) { sRepairCostAdj = 10; } // calculate repair cost, the more broken it is the more it costs, and the difficulty of repair it is also a factor uiRepairCost = (UINT32)( uiItemCost * ( sRepairCostAdj * (100 - bItemCondition) / ((FLOAT)100 * 100) )); /* //if the price is not diviseble by 10, make it so uiDifFrom10 = 10 - uiRepairCost % 10; if( uiDifFrom10 != 0 ) { uiRepairCost += uiDifFrom10; } */ if ( ItemIsARocketRifle( usItemIndex ) ) { // resetting imprinting for a rocket rifle costs something extra even if rifle is at 100% uiRepairCost += 100; } // anything repairable has to have a minimum price if ( uiRepairCost < MIN_REPAIR_COST ) { uiRepairCost = MIN_REPAIR_COST; } return( uiRepairCost ); } void SetSpecialItemInfoToDefaults( SPECIAL_ITEM_INFO *pSpclItemInfo ) { UINT8 ubCnt; memset( pSpclItemInfo, 0, sizeof( SPECIAL_ITEM_INFO ) ); pSpclItemInfo->bItemCondition = 100; pSpclItemInfo->ubImprintID = NO_PROFILE; for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { pSpclItemInfo->usAttachment[ ubCnt ] = NONE; pSpclItemInfo->bAttachmentStatus[ ubCnt ] = 0; } } void SetSpecialItemInfoFromObject( SPECIAL_ITEM_INFO *pSpclItemInfo, OBJECTTYPE *pObject ) { UINT8 ubCnt; memset(pSpclItemInfo, 0, sizeof( SPECIAL_ITEM_INFO ) ); if( Item[ pObject->usItem ].usItemClass == IC_AMMO ) { // ammo condition is always 100, don't use status, which holds the #bullets pSpclItemInfo->bItemCondition = 100; } else { pSpclItemInfo->bItemCondition = pObject->bStatus[ 0 ]; } // only guns currently have imprintID properly initialized... if ( Item[ pObject->usItem ].usItemClass == IC_GUN) { pSpclItemInfo->ubImprintID = pObject->ubImprintID; } else { // override garbage imprintIDs (generally 0) for non-guns pSpclItemInfo->ubImprintID = NO_PROFILE; } for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if( pObject->usAttachItem[ ubCnt ] != NONE ) { // store what it is pSpclItemInfo->usAttachment[ ubCnt ] = pObject->usAttachItem[ ubCnt ]; pSpclItemInfo->bAttachmentStatus[ ubCnt ] = pObject->bAttachStatus[ ubCnt ]; } else { pSpclItemInfo->usAttachment[ ubCnt ] = NONE; pSpclItemInfo->bAttachmentStatus[ ubCnt ] = 0; } } } BOOLEAN IsItemInfoSpecial( SPECIAL_ITEM_INFO *pSpclItemInfo ) { UINT8 ubCnt; // being damaged / in repairs makes an item special if ( pSpclItemInfo->bItemCondition != 100 ) { return(TRUE); } // being imprinted makes an item special if (pSpclItemInfo->ubImprintID != NO_PROFILE) { return(TRUE); } // having an attachment makes an item special for ( ubCnt = 0; ubCnt < MAX_ATTACHMENTS; ubCnt++ ) { if ( pSpclItemInfo->usAttachment[ ubCnt ] != NONE ) { return(TRUE); } } // otherwise, it's just a "perfect" item, nothing special about it return(FALSE); } BOOLEAN DoesItemAppearInDealerInventoryList( UINT8 ubArmsDealer, UINT16 usItemIndex, BOOLEAN fPurchaseFromPlayer ) { DEALER_POSSIBLE_INV *pDealerInv=NULL; UINT16 usCnt; // the others will buy only things that appear in their own "for sale" inventory lists pDealerInv = GetPointerToDealersPossibleInventory( ubArmsDealer ); Assert( pDealerInv != NULL ); // loop through the dealers' possible inventory and see if the item exists there usCnt = 0; while( pDealerInv[ usCnt ].sItemIndex != LAST_DEALER_ITEM ) { //if the initial dealer inv contains the required item, the dealer can sell the item if( pDealerInv[ usCnt ].sItemIndex == usItemIndex ) { // if optimal quantity listed is 0, it means dealer won't sell it himself, but will buy it from the player! if ( ( pDealerInv[ usCnt ].ubOptimalNumber > 0 ) || fPurchaseFromPlayer ) { return( TRUE ); } } usCnt++; } return( FALSE ); } UINT16 CalcValueOfItemToDealer( UINT8 ubArmsDealer, UINT16 usItemIndex, BOOLEAN fDealerSelling ) { UINT16 usBasePrice; UINT8 ubItemPriceClass; UINT8 ubDealerPriceClass; UINT16 usValueToThisDealer; usBasePrice = Item[ usItemIndex ].usPrice; if ( usBasePrice == 0 ) { // worthless to any dealer return( 0 ); } // figure out the price class this dealer prefers switch ( ubArmsDealer ) { case ARMS_DEALER_JAKE: ubDealerPriceClass = PRICE_CLASS_JUNK; break; case ARMS_DEALER_KEITH: ubDealerPriceClass = PRICE_CLASS_CHEAP; break; case ARMS_DEALER_FRANZ: ubDealerPriceClass = PRICE_CLASS_EXPENSIVE; break; // other dealers don't use this system default: if ( DoesItemAppearInDealerInventoryList( ubArmsDealer, usItemIndex, TRUE ) ) { return( usBasePrice ); } else { return( 0 ); } } // the rest of this function applies only to the "general" dealers ( Jake, Keith, and Franz ) // Micky & Gabby specialize in creature parts & such, the others don't buy these at all (exception: jars) if ( ( !Item[usItemIndex].jar ) && ( DoesItemAppearInDealerInventoryList( ARMS_DEALER_MICKY, usItemIndex, TRUE ) || DoesItemAppearInDealerInventoryList( ARMS_DEALER_GABBY, usItemIndex, TRUE ) ) ) { return( 0 ); } if ( ( ubArmsDealer == ARMS_DEALER_KEITH ) && ( Item [ usItemIndex].usItemClass & ( IC_GUN | IC_LAUNCHER ) ) ) { // Keith won't buy guns until the Hillbillies are vanquished if( CheckFact( FACT_HILLBILLIES_KILLED, KEITH ) == FALSE ) { return( 0 ); } } // figure out which price class it belongs to if ( usBasePrice < 100 ) { ubItemPriceClass = PRICE_CLASS_JUNK; } else if ( usBasePrice < 1000 ) { ubItemPriceClass = PRICE_CLASS_CHEAP; } else { ubItemPriceClass = PRICE_CLASS_EXPENSIVE; } if( !fDealerSelling ) { // junk dealer won't buy expensive stuff at all, expensive dealer won't buy junk at all if ( abs( (INT8) ubDealerPriceClass - (INT8) ubItemPriceClass ) == 2 ) { return( 0 ); } } // start with the base price usValueToThisDealer = usBasePrice; // if it's out of their preferred price class if ( ubDealerPriceClass != ubItemPriceClass ) { // exception: Gas (Jake's) // if ( usItemIndex != GAS_CAN ) if ( !Item[usItemIndex].gascan ) { // they pay only 1/3 of true value! usValueToThisDealer /= 3; } } // Tony specializes in guns, weapons, and ammo, so make others pay much less for that kind of stuff if ( DoesItemAppearInDealerInventoryList( ARMS_DEALER_TONY, usItemIndex, TRUE ) ) { // others pay only 1/2 of that value! usValueToThisDealer /= 2; } // minimum bet $1 ! if ( usValueToThisDealer == 0 ) { usValueToThisDealer = 1; } return( usValueToThisDealer ); } // this only exists to support saves made with game versions < 54 or 55! BOOLEAN LoadIncompleteArmsDealersStatus( HWFILE hFile, BOOLEAN fIncludesElgin, BOOLEAN fIncludesManny ) { UINT32 uiDealersSaved; UINT32 uiNumBytesRead; Assert( !fIncludesElgin || !fIncludesManny ); if ( !fIncludesElgin ) { // read 2 fewer element without Elgin or Manny in there... uiDealersSaved = NUM_ARMS_DEALERS - 2; } else { // read one fewer element without Elgin in there... uiDealersSaved = NUM_ARMS_DEALERS - 1; } // read in all other dealer's status if (!FileRead( hFile, gArmsDealerStatus, uiDealersSaved * sizeof( ARMS_DEALER_STATUS ), &uiNumBytesRead )) { return( FALSE ); } // read in all other dealer's inventory if (!FileRead( hFile, gArmsDealersInventory, uiDealersSaved * sizeof( DEALER_ITEM_HEADER ) * MAXITEMS, &uiNumBytesRead )) { return( FALSE ); } if ( !fIncludesElgin ) { // initialize Elgin now... InitializeOneArmsDealer( ARMS_DEALER_ELGIN ); } if ( !fIncludesManny ) { // initialize Manny now... InitializeOneArmsDealer( ARMS_DEALER_MANNY ); } return(TRUE); } BOOLEAN DealerItemIsSafeToStack( UINT16 usItemIndex ) { // basically any item type with nothing unique about it besides its status can be stacked in dealer's inventory boxes... // NOTE: This test is only applied to items already KNOWN to be perfect - special items are obviously not-stackable if ( Item[ usItemIndex ].usItemClass == IC_GUN ) { return( FALSE ); } /* if ( ItemSlotLimit( usItemIndex, BIGPOCK1POS ) > 1 ) { return( TRUE ); } */ return( TRUE ); } void GuaranteeMinimumAlcohol( UINT8 ubArmsDealer ) { GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, BEER, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, BEER ) / 3 ) ); GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, WINE, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, WINE ) / 3 ) ); GuaranteeAtLeastXItemsOfIndex( ubArmsDealer, ALCOHOL, ( UINT8 ) ( GetDealersMaxItemAmount( ubArmsDealer, ALCOHOL ) / 3 ) ); } BOOLEAN ItemIsARocketRifle( INT16 sItemIndex ) { if ( Item[sItemIndex].fingerprintid ) { return( TRUE ); } else { return( FALSE ); } } BOOLEAN GetArmsDealerShopHours( UINT8 ubArmsDealer, UINT32 *puiOpeningTime, UINT32 *puiClosingTime ) { SOLDIERTYPE *pSoldier; pSoldier = FindSoldierByProfileID( ArmsDealerInfo[ ubArmsDealer ].ubShopKeeperID, FALSE ); if ( pSoldier == NULL ) { return( FALSE ); } if ( ExtractScheduleDoorLockAndUnlockInfo( pSoldier, puiOpeningTime, puiClosingTime ) == FALSE ) { return( FALSE ); } Assert( *puiOpeningTime < *puiClosingTime ); return( TRUE ); } UINT32 CalculateOvernightRepairDelay( UINT8 ubArmsDealer, UINT32 uiTimeWhenFreeToStartIt, UINT32 uiMinutesToFix ) { UINT32 uiOpeningTime, uiClosingTime; UINT32 uiMinutesClosedOvernight; UINT32 uiDelayInDays = 0; UINT32 uiDoneToday; Assert( uiMinutesToFix > 0 ); // convert world time into 24hr military time for the day he's gonna start on it uiTimeWhenFreeToStartIt = uiTimeWhenFreeToStartIt % NUM_MIN_IN_DAY; if ( GetArmsDealerShopHours( ubArmsDealer, &uiOpeningTime, &uiClosingTime ) == FALSE ) { return( 0 ); } // if it won't get done by the end of a day while ( ( uiTimeWhenFreeToStartIt + uiMinutesToFix ) > uiClosingTime ) { // this is to handle existing saves with overnight repairs if ( uiTimeWhenFreeToStartIt < uiClosingTime ) { // he gets this much done before closing uiDoneToday = uiClosingTime - uiTimeWhenFreeToStartIt; // subtract how much he got done uiMinutesToFix -= uiDoneToday; Assert( uiMinutesToFix > 0 ); } // he starts back at it first thing in the morning uiTimeWhenFreeToStartIt = uiOpeningTime; uiDelayInDays++; } uiMinutesClosedOvernight = NUM_MIN_IN_DAY - ( uiClosingTime - uiOpeningTime ); return ( uiDelayInDays * uiMinutesClosedOvernight ); } UINT32 CalculateMinutesClosedBetween( UINT8 ubArmsDealer, UINT32 uiStartTime, UINT32 uiEndTime ) { UINT32 uiOpeningTime, uiClosingTime; UINT32 uiMinutesClosedOvernight; UINT32 uiDaysDifference = 0; UINT32 uiMinutesClosed = 0; Assert( uiStartTime <= uiEndTime ); if ( GetArmsDealerShopHours( ubArmsDealer, &uiOpeningTime, &uiClosingTime ) == FALSE ) { return( 0 ); } uiMinutesClosedOvernight = NUM_MIN_IN_DAY - ( uiClosingTime - uiOpeningTime ); // NOTE: this assumes stored are only closed overnight, so all we have to do is compare the day portion uiDaysDifference = ( uiEndTime / NUM_MIN_IN_DAY ) - ( uiStartTime / NUM_MIN_IN_DAY ); if ( uiDaysDifference >= 2 ) { // close for 1 less than that many full nights... uiMinutesClosed = ( uiDaysDifference - 1 ) * uiMinutesClosedOvernight; } // add partial day's closing // convert start and end times into 24hr military time uiStartTime = uiStartTime % NUM_MIN_IN_DAY; uiEndTime = uiEndTime % NUM_MIN_IN_DAY; // treat end time of midnight as 24:00 hours to prevent indefinite recursion and make formulas work if ( uiEndTime == 0 ) { uiEndTime = NUM_MIN_IN_DAY; } if ( uiStartTime == uiEndTime ) { if ( uiDaysDifference == 0 ) { return( 0 ); } else { uiMinutesClosed += uiMinutesClosedOvernight; } } if ( uiStartTime < uiEndTime ) { if ( uiStartTime < uiOpeningTime ) { // add how many minutes in the time range BEFORE the store opened that day uiMinutesClosed += ( min( uiOpeningTime, uiEndTime ) - uiStartTime ); } if ( uiEndTime > uiClosingTime ) { // add how many minutes in the time range AFTER the store closed that day uiMinutesClosed += ( uiEndTime - max( uiClosingTime, uiStartTime ) ); } } else { Assert( uiEndTime < uiStartTime ); // recursive calls! Add two separate times: before midnight, and after midnight uiMinutesClosed += CalculateMinutesClosedBetween( ubArmsDealer, uiStartTime, NUM_MIN_IN_DAY ); uiMinutesClosed += CalculateMinutesClosedBetween( ubArmsDealer, 0, uiEndTime ); } return ( uiMinutesClosed ); }