Files
source/Tactical/Arms Dealer Init.cpp
T
ChrisL bdcc920c63 Build1936
Minor adjustments to some of the rendering functions to grant a bit more control to the code.
Adjusted the mouse areas for key and item popup windows.
Added the pocket capacity indicator to sector inventory when using NewInv mode.
Added the right click option to sector inventory so you can view/manipulate stacks and view item descriptions without first equiping an item.
Updated sector inventory so that capacity values appear in all pockets.  Not just currently filled one.
Updated sector inventory so that newly placed items could be right-clicked on for details.
Increased the mouse region for the various stack popups so there is always room to close the popup without activating the description box.
Fixed a bug in the sector inventory description panel that caused a crash when trying to remove an attachment.
Fixed sector inventory so you can add attachments to weapons.
Fixed some weight issues.
Fixed a crash resulting from right-clicking in sector inventory while Item Desc window was already open.
Fixed the "Active Squad" message so it displays the squad that is actually activated.
Fixed a bug that allowed you to start a new game in NewInv mode while in 640x480 resolution.
Updated the Item Description windows so you can load a gun as well as unload.
Added new CTRL+SHIFT+D hotkey to delete all items in sector.
Added new CTRL+SHIFT+S hotkey to sell all items in sector, assuming you have the ALT+LMB option turned on in your INI file.
Fixed a problem with sector inventory item description where the status did not appear for items that couldn't stack.
Fixed a problem with Nails so he always starts with his leather jacket, even if you don't buy his gear.
Incorporated a failsafe so that NewInv mode can't be selected, nor NIV saves loaded, when no custom data path is set.
Add new IC_BELTCLIP item class.
Update pocket definition restrictions to use bitwise compare.
Add failsafe so that you can't merge LBENODEs.
Reset VC2005 so it doesn't point to a specific drive.


git-svn-id: https://ja2svn.mooo.com/source/ja2/trunk/GameSource/ja2_v1.13/Build@1936 3b4a5df2-a311-0410-b5c6-a8a6f20db521
2008-03-29 20:35:03 +00:00

2266 lines
69 KiB
C++

#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"
#include "Quests.h"
#include "Scheduling.h"
#endif
#include <list>
#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 ];
std::vector<DealerItemList> gArmsDealersInventory;
void AddAmmoToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT16 ubShotsLeft );
void AddItemToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE& object );
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 );
void ArmsDealerGetsFreshStock( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubNumItems );
BOOLEAN ItemContainsLiquid( UINT16 usItemIndex );
UINT8 DetermineDealerItemCondition( UINT8 ubArmsDealer, UINT16 usItemIndex );
BOOLEAN DoesItemAppearInDealerInventoryList( UINT8 ubArmsDealer, UINT16 usItemIndex, BOOLEAN fPurchaseFromPlayer );
void GuaranteeMinimumAlcohol( UINT8 ubArmsDealer );
BOOLEAN ItemIsARocketRifle( INT16 sItemIndex );
BOOLEAN GetArmsDealerShopHours( UINT8 ubArmsDealer, UINT32 *puiOpeningTime, UINT32 *puiClosingTime );
ARMS_DEALER_STATUS& ARMS_DEALER_STATUS::operator=(const OLD_ARMS_DEALER_STATUS_101& status)
{
this->initialize();
this->fOutOfBusiness = status.fOutOfBusiness;
this->fRepairDelayBeenUsed = status.fRepairDelayBeenUsed;
this->fUnusedKnowsPlayer = status.fUnusedKnowsPlayer;
this->ubSpecificDealerFlags = status.ubSpecificDealerFlags;
this->uiArmsDealersCash = status.uiArmsDealersCash;
this->uiTimePlayerLastInSKI = status.uiTimePlayerLastInSKI;
return *this;
}
void ARMS_DEALER_STATUS::initialize()
{
memset(this, 0, sizeof(ARMS_DEALER_STATUS));
}
void DEALER_SPECIAL_ITEM::initialize()
{
memset(this, 0, SIZEOF_DEALER_SPECIAL_ITEM_POD);
object.initialize();
}
bool DEALER_SPECIAL_ITEM::operator ==(const DEALER_SPECIAL_ITEM& compare)
{
if (this->bItemCondition == compare.bItemCondition &&
this->ubOwnerProfileId == compare.ubOwnerProfileId &&
this->ubQtyOnOrder == compare.ubQtyOnOrder &&
this->uiOrderArrivalTime == compare.uiOrderArrivalTime &&
this->uiRepairDoneTime == compare.uiRepairDoneTime) {
if (this->object == compare.object) {
return true;
}
}
return false;
}
void InitAllArmsDealers()
{
UINT8 ubArmsDealer;
ShutDownArmsDealers();
//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 ) );
gArmsDealersInventory.resize(gArmsDealersInventory.size() + 1);
//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()
{
gArmsDealersInventory.clear();
}
BOOLEAN SaveArmsDealerInventoryToSaveGameFile( HWFILE hFile )
{
UINT32 uiNumBytesWritten;
UINT8 ubArmsDealer;
//save the number of dealers to load
int dealers = NUM_ARMS_DEALERS;
if (!FileWrite( hFile, &dealers, sizeof( int ), &uiNumBytesWritten ))
{
return( FALSE );
}
//Save the arms dealers status
if (!FileWrite( hFile, gArmsDealerStatus, sizeof( gArmsDealerStatus ), &uiNumBytesWritten ))
{
return( FALSE );
}
//loop through all the dealers inventories
for( ubArmsDealer=0; ubArmsDealer<NUM_ARMS_DEALERS; ubArmsDealer++ )
{
int size = gArmsDealersInventory[ubArmsDealer].size();
if (!FileWrite( hFile, &size, sizeof( int ), &uiNumBytesWritten ))
{
return( FALSE );
}
//loop through this dealer's individual items
for (DealerItemList::iterator iter = gArmsDealersInventory[ubArmsDealer].begin();
iter != gArmsDealersInventory[ubArmsDealer].end(); ++iter) {
if (! iter->Save(hFile) ) {
return FALSE;
}
}
}
return( TRUE );
}
void DEALER_SPECIAL_ITEM::ConvertFrom101(OLD_DEALER_ITEM_HEADER_101& header, OLD_DEALER_SPECIAL_ITEM_101& special, int ubArmsDealer, int usItemIndex)
{
this->bItemCondition = special.oldInfo.bItemCondition;
if (this->bItemCondition == 0) {
return;
}
this->uiRepairDoneTime = special.uiRepairDoneTime;
this->ubOwnerProfileId = special.ubOwnerProfileId;
this->uiOrderArrivalTime = 0;
this->ubQtyOnOrder = 0;
int condition = this->bItemCondition;
if (this->IsUnderRepair()) {
condition *= -1;
}
CreateObjectForDealer(usItemIndex, condition, 1, &(this->object));
this->object[0]->data.ubImprintID = special.oldInfo.ubImprintID;
for (int x = 0; x < OLD_MAX_ATTACHMENTS_101; ++x) {
if (special.oldInfo.usAttachment[x] && special.oldInfo.bAttachmentStatus[x] ) {
CreateObjectForDealer(special.oldInfo.usAttachment[x], special.oldInfo.bAttachmentStatus[x], 1, &gTempObject);
this->object.AttachObject(0, &gTempObject, FALSE);
}
}
gArmsDealersInventory[ubArmsDealer].push_back(*this);
};
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;
static int numPerfectItems[MAXITEMS];
//loop through all the arms dealers
for( ubArmsDealer=0;ubArmsDealer<NUM_ARMS_DEALERS;ubArmsDealer++ )
{
if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
continue;
//if the arms dealer isn't supposed to have any items (includes all repairmen)
if( ArmsDealerInfo[ ubArmsDealer ].uiFlags & ARMS_DEALER_HAS_NO_INVENTORY )
continue;
memset (&numPerfectItems, 0, sizeof(numPerfectItems));
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == true && iter->bItemCondition == 100) {
numPerfectItems[iter->object.usItem] += iter->object.ubNumberOfObjects;
}
}
//loop through all items of the same type
for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
{
if ( Item[usItemIndex].usItemClass == 0 )
break;
//if there are some of these in stock
if( numPerfectItems[usItemIndex] > 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, numPerfectItems[usItemIndex]), FALSE);
}
else
{
ubItemsSold = HowManyItemsAreSold( ubArmsDealer, usItemIndex, numPerfectItems[usItemIndex], FALSE);
}
if ( ubItemsSold > 0)
{
RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, ubItemsSold);
}
}
}
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
// next, try to sell all the used ones, gotta do these one at a time so we can remove them by element
// don't worry about negative condition, repairmen can't come this far, they don't sell!
if (iter->bItemCondition < 100 && iter->bItemCondition > 0) {
if ( iter->ItemIsInInventory() == true) {
// try selling just this one
if (HowManyItemsAreSold( ubArmsDealer, iter->object.usItem, 1, TRUE) > 0)
{
iter = gArmsDealersInventory[ ubArmsDealer ].erase(iter);
if (iter == gArmsDealersInventory[ ubArmsDealer ].end()) {
break;
}
}
}
}
}
}
}
void DailyCheckOnItemQuantities()
{
UINT8 ubArmsDealer;
UINT16 usItemIndex;
UINT8 ubMaxSupply;
UINT8 ubNumItems;
UINT32 uiArrivalDay;
UINT8 ubReorderDays;
//loop through all the arms dealers
for( ubArmsDealer=0;ubArmsDealer<NUM_ARMS_DEALERS;ubArmsDealer++ )
{
if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
continue;
//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 )
continue;
int numTotalItems[MAXITEMS] = { 0 };
bool itemsAreOnOrder[MAXITEMS] = { false };
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->object.exists() == true) {
if (iter->IsBeingOrdered() == false) {
numTotalItems[iter->object.usItem] += iter->object.ubNumberOfObjects;
}
else {
itemsAreOnOrder[iter->object.usItem] = true;
//and today is the day the items come in
if( iter->uiOrderArrivalTime >= GetWorldDay() )
{
iter->ubQtyOnOrder = 0;
iter->uiOrderArrivalTime = 0;
}
}
}
}
//loop through all items of the same type
for( usItemIndex = 1; usItemIndex < MAXITEMS; usItemIndex++ )
{
if ( Item[usItemIndex].usItemClass == 0 )
break;
//if the dealer can sell the item type
if( CanDealerTransactItem( ubArmsDealer, usItemIndex, FALSE ) )
{
//if there are no items on order
if ( itemsAreOnOrder[ usItemIndex ] == false )
{
ubMaxSupply = GetDealersMaxItemAmount( ubArmsDealer, usItemIndex );
//if the qty on hand is half the desired amount or fewer
if( numTotalItems[ usItemIndex ] <= (INT32)( ubMaxSupply / 2 ) )
{
//determine if the item can be restocked (assume new, use items aren't checked for until the stuff arrives)
if (ItemTransactionOccurs( ubArmsDealer, usItemIndex, DEALER_BUYING, FALSE ))
{
// figure out how many items to reorder (items are reordered an entire batch at a time)
ubNumItems = HowManyItemsToReorder( ubMaxSupply, numTotalItems[ usItemIndex ] );
// if this is the first day the player is eligible to have access to this thing
if ( gArmsDealerStatus[ubArmsDealer].fPreviouslyEligible[ usItemIndex ] == false )
{
// eliminate the ordering delay and stock the items instantly!
// This is just a way to reward the player right away for making progress without the reordering lag...
ArmsDealerGetsFreshStock( ubArmsDealer, usItemIndex, ubNumItems );
}
else
{
if ( ( ubArmsDealer == ARMS_DEALER_TONY ) || ( ubArmsDealer == ARMS_DEALER_DEVIN ) )
{
// the stuff Tony and Devin sell is imported, so it takes longer to arrive (for game balance)
ubReorderDays = ( UINT8) ( 2 + Random( 2 ) ); // 2-3 days
}
else
{
ubReorderDays = ( UINT8) ( 1 + Random( 2 ) ); // 1-2 days
}
//Determine when the inventory should arrive
uiArrivalDay = GetWorldDay() + ubReorderDays; // consider changing this to minutes
// post new order
OrderDealerItems(ubArmsDealer, usItemIndex, ubNumItems, uiArrivalDay);
}
}
}
}
}
}
}
}
void OrderDealerItems(int armsDealer, int usItem, int numItems, int arrivalDay)
{
int perfectItems = 0;
for (int x = 0; x < numItems; ++x) {
int ubItemCondition = DetermineDealerItemCondition( armsDealer, usItem);
// if the item is brand new
if ( ubItemCondition == 100)
{
perfectItems++;
}
else
{
// add a used item with that condition to his inventory
gArmsDealersInventory[ armsDealer ].push_back(DEALER_SPECIAL_ITEM());
DEALER_SPECIAL_ITEM* pOrder = &(gArmsDealersInventory[ armsDealer ].back());
pOrder->ubQtyOnOrder = 1;
pOrder->uiOrderArrivalTime = arrivalDay;
pOrder->bItemCondition = ubItemCondition;
CreateObjectForDealer(usItem, ubItemCondition, 1, &(pOrder->object));
}
}
// now add all the perfect ones, in one shot
if ( perfectItems > 0)
{
gArmsDealersInventory[ armsDealer ].push_back(DEALER_SPECIAL_ITEM());
DEALER_SPECIAL_ITEM* pOrder = &(gArmsDealersInventory[ armsDealer ].back());
pOrder->ubQtyOnOrder = perfectItems;
pOrder->uiOrderArrivalTime = arrivalDay;
/*CHRISL: I can't say for absolute, but I think this is where we are causing the "100rnd" bug. We're setting
a bItemConditon 100 regardless of what the item is. But if we're working with ammo, we don't want
bItemCondition to be 100 even if the item is "perfect" (i.e., a fully loaded mag). We need a condition
so we set this value to magSize when dealing with ammo*/
if(Item[usItem].usItemClass == IC_AMMO)
pOrder->bItemCondition = Magazine[Item[usItem].ubClassIndex].ubMagSize;
else
pOrder->bItemCondition = 100;
CreateObjectForDealer(usItem, 100, perfectItems, &(pOrder->object));
}
}
void ConvertCreatureBloodToElixir( void )
{
UINT8 ubBloodAvailable = 0;
UINT8 ubAmountToConvert;
for (DealerItemList::iterator iter = gArmsDealersInventory[ ARMS_DEALER_GABBY ].begin();
iter != gArmsDealersInventory[ ARMS_DEALER_GABBY ].end(); ++iter) {
if (iter->ItemIsInInventory() == true && iter->object.usItem == JAR_CREATURE_BLOOD) {
ubBloodAvailable += iter->object.ubNumberOfObjects;
}
}
if ( ubBloodAvailable )
{
// start converting blood into elixir!
//ubAmountToConvert = (UINT8) __min( 5 + Random( 3 ), ubBloodAvailable );
ubAmountToConvert = ubBloodAvailable;
RemoveItemFromArmsDealerInventory( ARMS_DEALER_GABBY, JAR_CREATURE_BLOOD, 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 )
{
if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
return;
//ADB, ya, a whole 1 line of extra code!
// not permitted for repair dealers - would take extra code to avoid counting items under repair!
//Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
int numTotalItems[MAXITEMS] = { 0 };
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin(); iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == true && iter->IsUnderRepair() == false) {
numTotalItems[iter->object.usItem] += iter->object.ubNumberOfObjects;
}
}
UINT32 uiTotalNumberOfItems = 0, uiRandomChoice;
UINT32 uiIndex;
UINT16 usItemIndex=0;
std::vector<int> usAvailableItems;
std::vector<UINT32> ubNumberOfAvailableItem;
//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( numTotalItems[usItemIndex] > 0)
{
//if the item is of the same dealer item type
if( uiDealerItemType & GetArmsDealerItemTypeFromItemNumber( usItemIndex ) )
{
usAvailableItems.push_back(usItemIndex);
//if the dealer item type is ammo
if( uiDealerItemType == ARMS_DEALER_AMMO )
{
// all ammo of same type counts as only one item
ubNumberOfAvailableItem.push_back(1);
uiTotalNumberOfItems++;
}
else
{
// items being repaired don't count against the limit
ubNumberOfAvailableItem.push_back(numTotalItems[usItemIndex]);
uiTotalNumberOfItems += ubNumberOfAvailableItem.back();
}
}
}
}
//if there is more of the given type than we want
INT32 uiItemsToRemove = usAvailableItems.size() - ubMaxNumberOfItemType;
while (uiItemsToRemove > 0)
{
uiRandomChoice = Random( uiTotalNumberOfItems );
for ( uiIndex = 0; uiIndex < usAvailableItems.size(); uiIndex++ )
{
if ( uiRandomChoice <= ubNumberOfAvailableItem[ uiIndex ] )
{
usItemIndex = usAvailableItems[ uiIndex ];
if ( uiDealerItemType == ARMS_DEALER_AMMO )
{
// remove all of them, since each ammo item counts as only one "item" here
// ammo will always be only condition 100, there's never any in special slots
RemoveItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, ubNumberOfAvailableItem[uiIndex] );
}
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
usAvailableItems[ uiIndex ] = usAvailableItems.back();
usAvailableItems.pop_back();
ubNumberOfAvailableItem[ uiIndex ] = ubNumberOfAvailableItem.back();
ubNumberOfAvailableItem.pop_back();
// 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( gOldArmsDealersInventory[ 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, gOldArmsDealersInventory[ ubArmsDealer ][ usItemIndex ].ubTotalItems );
}
else
{
// pick 1 random one, don't care about its condition
RemoveRandomItemFromArmsDealerInventory( ubArmsDealer, usItemIndex, 1 );
}
uiItemsToRemove--;
if( uiItemsToRemove == 0)
break;
}
}
}
}
*/
}
}
void GuaranteeAtLeastOneItemOfType( UINT8 ubArmsDealer, UINT32 uiDealerItemType )
{
UINT16 usItemIndex;
UINT8 ubChance;
UINT32 uiTotalChances = 0;
UINT32 uiIndex, uiRandomChoice;
if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
return;
//ADB, ya, a whole 1 line of extra code!
// not permitted for repair dealers - would take extra code to avoid counting items under repair!
//Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
int numTotalItems[MAXITEMS] = { 0 };
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == true && iter->IsUnderRepair() == false) {
numTotalItems[iter->object.usItem] += iter->object.ubNumberOfObjects;
}
}
std::vector<UINT16> usAvailableItems;
std::vector<UINT8> ubChanceForAvailableItem;
//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( numTotalItems[usItemIndex] > 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 )
{
usAvailableItems.push_back(usItemIndex);
ubChanceForAvailableItem.push_back(ubChance);
uiTotalChances += ubChance;
}
}
}
}
// if there aren't any such items, the following loop would never finish, so quit before trying it!
if (usAvailableItems.empty() == true)
{
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 < usAvailableItems.size(); uiIndex++ )
{
if ( uiRandomChoice <= ubChanceForAvailableItem[ uiIndex ] )
{
ArmsDealerGetsFreshStock( ubArmsDealer, usAvailableItems[ uiIndex ], 1 );
return;
}
else
{
// next item!
uiRandomChoice -= ubChanceForAvailableItem[ uiIndex ];
}
}
Assert(false);
// internal logic failure!
}
void GuaranteeAtLeastXItemsOfIndex( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany )
{
if( gArmsDealerStatus[ ubArmsDealer ].fOutOfBusiness )
return;
//ADB, ya, a whole 1 line of extra code!
// not permitted for repair dealers - would take extra code to avoid counting items under repair!
//Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
int itemsIHave = 0;
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == true
&& iter->object.usItem == usItemIndex
&& iter->IsUnderRepair() == false) {
itemsIHave += iter->object.ubNumberOfObjects;
//if there are any of these in stock
if( itemsIHave >= 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 - itemsIHave ) );
}
}
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;
// CHRISL: Itendify new class for inventory system
case IC_LBEGEAR:
case IC_BELTCLIP:
return( 0 );
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<NUM_ARMS_DEALERS; cnt++ )
{
if( ArmsDealerInfo[ cnt ].ubShopKeeperID == ubMercID )
return( TRUE );
}
return( FALSE );
}
INT8 GetArmsDealerIDFromMercID( UINT8 ubMercID )
{
INT8 cnt;
//loop through the list of arms dealers
for( cnt=0; cnt<NUM_ARMS_DEALERS; cnt++ )
{
if( ArmsDealerInfo[ cnt ].ubShopKeeperID == ubMercID )
return( cnt );
}
return( -1 );
}
UINT8 GetTypeOfArmsDealer( UINT8 ubDealerID )
{
return( ArmsDealerInfo[ ubDealerID ].ubTypeOfArmsDealer );
}
BOOLEAN DoesDealerDoRepairs( UINT8 ubArmsDealer )
{
if( ArmsDealerInfo[ ubArmsDealer ].ubTypeOfArmsDealer == ARMS_DEALER_REPAIRS )
return( TRUE );
else
return( FALSE );
}
BOOLEAN RepairmanIsFixingItemsButNoneAreDoneYet( UINT8 ubProfileID )
{
INT8 bArmsDealer;
BOOLEAN fHaveOnlyUnRepairedItems=FALSE;
bArmsDealer = GetArmsDealerIDFromMercID( ubProfileID );
if( bArmsDealer == -1 )
return( FALSE );
//if the dealer is not a repair dealer, return
if( !DoesDealerDoRepairs( bArmsDealer ) )
return( FALSE );
//loop through the dealers inventory and check if there are only unrepaired items
for (DealerItemList::iterator iter = gArmsDealersInventory[ bArmsDealer ].begin();
iter != gArmsDealersInventory[ bArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == false) {
continue;
}
if ( Item[iter->object.usItem].usItemClass == 0 )
continue;
if( iter->IsUnderRepair() == true )
{
//if the item has been repaired
if( iter->uiRepairDoneTime <= GetWorldTotalMin() )
{
//A repair item is ready, therefore, return false
return( FALSE );
}
else
{
fHaveOnlyUnRepairedItems = TRUE;
}
}
}
return( fHaveOnlyUnRepairedItems );
}
BOOLEAN CanDealerTransactItem( UINT8 ubArmsDealer, UINT16 usItemIndex, BOOLEAN fPurchaseFromPlayer )
{
switch ( ArmsDealerInfo[ ubArmsDealer ].ubTypeOfArmsDealer )
{
case ARMS_DEALER_SELLS_ONLY:
if ( fPurchaseFromPlayer )
{
// this dealer only sells stuff to player, so he can't buy anything from him
return( FALSE );
}
break;
case ARMS_DEALER_BUYS_ONLY:
if ( !fPurchaseFromPlayer )
{
// this dealer only buys stuff from player, so he can't sell anything to him
return( FALSE );
}
break;
case ARMS_DEALER_BUYS_SELLS:
switch ( ubArmsDealer )
{
case ARMS_DEALER_JAKE:
case ARMS_DEALER_KEITH:
case ARMS_DEALER_FRANZ:
if ( fPurchaseFromPlayer )
{
// these guys will buy nearly anything from the player, regardless of what they carry for sale!
return( CalcValueOfItemToDealer( ubArmsDealer, usItemIndex, FALSE ) > 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);
}
void ArmsDealerGetsFreshStock( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubNumItems )
{
UINT8 ubCnt;
UINT8 ubItemCondition = 0;
UINT8 ubPerfectOnes = 0;
// 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
CreateObjectForDealer(usItemIndex, ubItemCondition, 1, &gTempObject);
AddObjectToArmsDealerInventory( ubArmsDealer, &gTempObject );
}
}
// now add all the perfect ones, in one shot
if ( ubPerfectOnes > 0)
{
CreateObjectForDealer(usItemIndex, ubItemCondition, ubPerfectOnes, &gTempObject);
AddObjectToArmsDealerInventory( ubArmsDealer, &gTempObject );
}
}
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 );
}
bool ItemIsSpecial(DEALER_SPECIAL_ITEM& item)
{
for (int x = 0; x < item.object.ubNumberOfObjects; ++x) {
if (item.object[x]->attachments.empty() == false) {
//items with attachments are special
return true;
}
}
if (item.IsBeingOrdered() == true) {
//item being ordered
return true;
}
if (item.IsUnderRepair() == true) {
//items in for repairs are special
return true;
}
return false;
}
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 ubHowManyInForRepairs = 0;
//if the dealer is not a repair dealer, no need to count, return 0
if( !DoesDealerDoRepairs( ubArmsDealer ) )
return( 0 );
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin();
iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->object.exists() == true && iter->object.usItem == usItemIndex) {
if( iter->IsUnderRepair() == true )
{
ubHowManyInForRepairs++;
}
}
}
return( ubHowManyInForRepairs );
}
void AddObjectToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE *pObject )
{
// split up all the components of an objecttype and add them as seperate items into the dealer's inventory
OBJECTTYPE seperateObject;
int total = 0;
int usItem = pObject->usItem;
while (pObject->exists() == true)
{
pObject->MoveThisObjectTo(seperateObject, 1);
StackedObjectData* pData = seperateObject[0];
// loop through any detachable attachments and add them as seperate items
for (attachmentList::iterator iter = seperateObject[0]->attachments.begin(); iter != seperateObject[0]->attachments.end();) {
// 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[ iter->usItem ].inseparable ) )
{
// add this particular attachment (they can't be imprinted, or themselves have attachments!)
AddObjectToArmsDealerInventory( ubArmsDealer, &(*iter) );
iter = pData->attachments.erase(iter);
if (iter == pData->attachments.end()) {
break;
}
}
else {
++iter;
}
}
int usGunAmmoItem;
int ubGunShotsLeft;
int bItemCondition;
switch ( Item [ usItem ].usItemClass )
{
case IC_GUN:
usGunAmmoItem = seperateObject[0]->data.gun.usGunAmmoItem;
ubGunShotsLeft = seperateObject[0]->data.gun.ubGunShotsLeft;
bItemCondition = seperateObject[0]->data.gun.bGunAmmoStatus;
// if the gun was jammed, this will forget about the jam (i.e. dealer immediately unjams anything he buys)
//reset everything but bGunStatus
seperateObject[0]->data.gun.usGunAmmoItem = 0;
seperateObject[0]->data.gun.ubGunShotsLeft = 0;
seperateObject[0]->data.gun.bGunAmmoStatus = 0;
seperateObject[0]->data.gun.ubGunState = 0;
seperateObject[0]->data.gun.ubGunAmmoType = 0;
// add the gun (keeps the object's status and imprintID)
AddItemToArmsDealerInventory( ubArmsDealer, seperateObject );
// if any GunAmmoItem is specified
if( usGunAmmoItem != NONE)
{
// if it's regular ammo
if( Item[ usGunAmmoItem ].usItemClass == IC_AMMO )
{
// and there are some remaining
if ( ubGunShotsLeft > 0 )
{
// add the bullets of its remaining ammo
AddAmmoToArmsDealerInventory( ubArmsDealer, usGunAmmoItem, ubGunShotsLeft );
}
}
else // assume it's attached ammo (mortar shells, grenades)
{
// add the launchable item (can't be imprinted, or have attachments!)
// if the gun it was in was jammed, get rid of the negative status now
if ( bItemCondition < 0 )
{
bItemCondition *= -1;
}
CreateObjectForDealer(usGunAmmoItem, bItemCondition, 1, &seperateObject);
AddItemToArmsDealerInventory( ubArmsDealer, seperateObject );
}
}
break;
case IC_AMMO:
// add the contents of each magazine (multiple mags may have vastly different #bullets left)
total += seperateObject[0]->data.ubShotsLeft;
break;
default:
// add each object seperately (multiple objects may have vastly different statuses, keep any imprintID)
if (seperateObject[0]->data.objectStatus == 100) {
total += 1;
}
else {
AddItemToArmsDealerInventory( ubArmsDealer, seperateObject );
}
}
}
switch ( Item [ usItem ].usItemClass )
{
case IC_GUN:
break;
case IC_AMMO:
AddAmmoToArmsDealerInventory( ubArmsDealer, usItem, total );
break;
default:
if (total) {
CreateObjectForDealer(usItem, 100, total, &seperateObject);
AddItemToArmsDealerInventory( ubArmsDealer, seperateObject );
}
break;
}
}
void AddAmmoToArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT16 ubShotsLeft )
{
// Ammo only, please!!!
if (Item [ usItemIndex ].usItemClass != IC_AMMO )
{
Assert(0);
return;
}
if ( ubShotsLeft == 0)
{
return;
}
UINT16 ubMagCapacity = Magazine[ Item[ usItemIndex ].ubClassIndex ].ubMagSize;
int numFullMags = 0;
if ( ubShotsLeft >= ubMagCapacity )
{
// add however many FULL magazines the #shot left represents
numFullMags = 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
UINT16 *pubStrayAmmo = &(gArmsDealerStatus[ubArmsDealer].ubStrayAmmo[ usItemIndex ]);
*pubStrayAmmo += ubShotsLeft;
// if dealer has accumulated enough stray ammo to make another full magazine, convert it!
if ( *pubStrayAmmo >= ubMagCapacity )
{
numFullMags += *pubStrayAmmo / ubMagCapacity;
*pubStrayAmmo %= ubMagCapacity;
}
// I know, I know, this is getting pretty anal... But what the hell, it was easy enough to do. ARM.
}
if (numFullMags) {
CreateObjectForDealer(usItemIndex, 100, numFullMags, &gTempObject);
AddItemToArmsDealerInventory( ubArmsDealer, gTempObject );
}
}
//Use AddObjectToArmsDealerInventory() instead of this when converting a complex item in OBJECTTYPE format.
void AddItemToArmsDealerInventory( UINT8 ubArmsDealer, OBJECTTYPE& object )
{
//we can only add items to a stack if the item has no attachments (not even default)
//and if it is either perfect or ammo (and same amount of ammo)
if (object[0]->attachments.empty() == true &&
(object[0]->data.objectStatus == 100 || Item [ object.usItem ].usItemClass == IC_AMMO)) {
//first find existing items with same perfect status, if found add to that, else create new one
for (DealerItemList::iterator iter = gArmsDealersInventory[ubArmsDealer].begin();
iter != gArmsDealersInventory[ubArmsDealer].end(); ++iter) {
if (iter->ItemIsInInventory() == true &&
iter->object.usItem == object.usItem &&
iter->bItemCondition == object[0]->data.objectStatus) {
//won't find objects in for repair
//make sure it's not something special, objects with default attachments aren't handled, but oh well
if (ItemIsSpecial(*iter) == false) {
iter->object.AddObjectsToStack(object);
if (object.exists() == false) {
return;
}
}
}
}
}
if (object.exists() == true) {
gArmsDealersInventory[ubArmsDealer].push_back(DEALER_SPECIAL_ITEM());
DEALER_SPECIAL_ITEM* pItem = &(gArmsDealersInventory[ubArmsDealer].back());
pItem->object = object;
pItem->bItemCondition = object[0]->data.objectStatus;
}
}
// removes ubHowMany items of usItemIndex with the matching Info from dealer ubArmsDealer
void RemoveItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany, OBJECTTYPE *pObj )
{
BOOLEAN match = TRUE;
if ( ubHowMany == 0)
{
return;
}
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin(); iter != gArmsDealersInventory[ ubArmsDealer ].end(); iter++){
if (iter->ItemIsInInventory() == true && iter->object.usItem == usItemIndex) {
if(pObj != 0)
{
for(unsigned int i = 0; i < ubHowMany; i++)
{
if((*pObj)[i]->operator == (*iter->object[i]))
match = TRUE;
else
match = FALSE;
}
}
if(match == TRUE)
{
if (ubHowMany >= iter->object.ubNumberOfObjects) {
ubHowMany -= iter->object.ubNumberOfObjects;
iter = gArmsDealersInventory[ ubArmsDealer ].erase(iter);
DealerItemList::iterator iterEnd = gArmsDealersInventory[ ubArmsDealer ].end();
if (ubHowMany > 0 || iter == gArmsDealersInventory[ ubArmsDealer ].end()) {
return;
}
}
else {
iter->object.RemoveObjectsFromStack(ubHowMany);
return;
}
}
}
}
}
void RemoveRandomItemFromArmsDealerInventory( UINT8 ubArmsDealer, UINT16 usItemIndex, UINT8 ubHowMany )
{
//ADB, ya, a whole 1 line of extra code!
// not permitted for repair dealers - would take extra code to avoid counting items under repair!
//Assert( !DoesDealerDoRepairs( ubArmsDealer ) );
std::vector<DealerItemList::iterator> foundObjects;
while ( ubHowMany > 0)
{
int numItems = 0;
foundObjects.clear();
for (DealerItemList::iterator iter = gArmsDealersInventory[ ubArmsDealer ].begin(); iter != gArmsDealersInventory[ ubArmsDealer ].end(); ++iter) {
if (iter->ItemIsInInventory() == true && iter->object.usItem == usItemIndex && iter->IsUnderRepair() == false) {
foundObjects.push_back(iter);
numItems += iter->object.ubNumberOfObjects;
}
}
if (numItems == 0) {
return;
}
// pick a random one to get rid of
UINT8 ubWhichOne = (UINT8)Random(numItems);
std::vector<DealerItemList::iterator>::iterator iterToRemove = foundObjects.begin();
for (int x = (*iterToRemove)->object.ubNumberOfObjects - 1; x < ubWhichOne;) {
x += (*iterToRemove)->object.ubNumberOfObjects;
if(x < ubWhichOne)
++iterToRemove;
}
if ((*iterToRemove)->object.ubNumberOfObjects == 1) {
gArmsDealersInventory[ubArmsDealer].erase(*iterToRemove);
}
else {
//not totally random, but close enough
(*iterToRemove)->object.RemoveObjectsFromStack(1);
}
ubHowMany--;
}
}
BOOLEAN AddDeadArmsDealerItemsToWorld( UINT8 ubMercID )
{
INT8 bArmsDealer;
//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 );
}
SOLDIERTYPE *pSoldier;
// 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 (DealerItemList::iterator iter = gArmsDealersInventory[bArmsDealer].begin();
iter != gArmsDealersInventory[bArmsDealer].end(); ++iter) {
if (iter->ItemIsInInventory() == true) {
AddItemToPool( pSoldier->sInitialGridNo, &(iter->object), INVISIBLE, 0, 0, 0 );
}
}
gArmsDealersInventory[bArmsDealer].clear();
//if the dealer has money
if( gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash > 0 )
{
//Create the object
if( !CreateMoney( gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash, &gTempObject ) )
{
return( FALSE );
}
//add the money item to the dealers feet
AddItemToPool( pSoldier->sInitialGridNo, &gTempObject, INVISIBLE, 0, 0, 0 );
gArmsDealerStatus[ bArmsDealer ].uiArmsDealersCash = 0;
}
return( TRUE );
}
void CreateObjectForDealer( int usItem, int status, int numObjects, OBJECTTYPE *pObject )
{
//if the item condition is below 0, the item is in for repairs, so flip the sign
if( status < 0 )
{
status *= -1;
}
CreateItems(usItem, status, numObjects, pObject);
MakeObjectOutOfDealerItems(NULL, pObject);
return;
}
void MakeObjectOutOfDealerItems( DEALER_SPECIAL_ITEM *pSpclItemInfo, OBJECTTYPE *pObject )
{
INT16 bItemCondition;
if (pSpclItemInfo != NULL) {
bItemCondition = pSpclItemInfo->bItemCondition;
//we might be just calling this to setup the object the dealer has
if (pObject != &(pSpclItemInfo->object)) {
*pObject = pSpclItemInfo->object;
}
}
else {
bItemCondition = (*pObject)[0]->data.objectStatus;
}
//if the item condition is below 0, the item is in for repairs, so flip the sign
if( bItemCondition < 0 )
{
bItemCondition *= -1;
}
for (int subObject = 0; subObject < pObject->ubNumberOfObjects; ++subObject) {
(*pObject)[subObject]->data.objectStatus = bItemCondition;
// 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)[subObject]->data.gun.ubGunShotsLeft = 0;
}
}
}
void GiveObjectToArmsDealerForRepair( UINT8 ubArmsDealer, OBJECTTYPE *pObject, UINT8 ubOwnerProfileId )
{
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)[0]->data.objectStatus < 100 ) || ItemIsARocketRifle( pObject->usItem ) );
/* ARM: Can now repair with removeable attachments still attached...
// d) Already stripped of all *detachable* attachments
*/
// 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)[0]->data.gun.usGunAmmoItem != NONE)
{
// it better be regular ammo, and empty
Assert( Item[ (*pObject)[0]->data.gun.usGunAmmoItem ].usItemClass == IC_AMMO );
Assert( (*pObject)[0]->data.gun.ubGunShotsLeft == 0 );
}
}
// 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, ubOwnerProfileId );
}
//PLEASE: Use GiveObjectToArmsDealerForRepair() instead of this when repairing a item in OBJECTTYPE format.
void GiveItemToArmsDealerforRepair( UINT8 ubArmsDealer, OBJECTTYPE* pObject, UINT8 ubOwnerProfileId )
{
UINT32 uiTimeWhenFreeToStartIt;
UINT32 uiMinutesToFix;
UINT32 uiMinutesShopClosedBeforeItsDone;
UINT32 uiDoneWhen;
Assert( DoesDealerDoRepairs( ubArmsDealer ) );
Assert( (*pObject)[0]->data.objectStatus > 0 );
Assert( ( (*pObject)[0]->data.objectStatus < 100 ) || ItemIsARocketRifle( pObject->usItem ) );
// 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 = CalculateObjectItemRepairTime( ubArmsDealer, pObject );
uiMinutesShopClosedBeforeItsDone = CalculateOvernightRepairDelay( ubArmsDealer, uiTimeWhenFreeToStartIt, uiMinutesToFix );
// clock time when this will finally be ready
uiDoneWhen = uiTimeWhenFreeToStartIt + uiMinutesToFix + uiMinutesShopClosedBeforeItsDone;
gArmsDealersInventory[ ubArmsDealer ].push_back(DEALER_SPECIAL_ITEM());
DEALER_SPECIAL_ITEM* pItem = &gArmsDealersInventory[ ubArmsDealer ].back();
pItem->object = *pObject;
// Negate the status
pItem->bItemCondition = (*pObject)[0]->data.objectStatus * -1;
//Set the time at which item will be fixed
pItem->uiRepairDoneTime = uiDoneWhen;
//Remember the original owner of the item
pItem->ubOwnerProfileId = ubOwnerProfileId;
}
UINT32 WhenWillRepairmanBeAllDoneRepairing( UINT8 ubArmsDealer )
{
UINT32 uiWhenFree;
Assert( DoesDealerDoRepairs( ubArmsDealer ) );
// if nothing is in for repairs, he'll be free RIGHT NOW!
uiWhenFree = GetWorldTotalMin();
//loop through the dealers inventory
for (DealerItemList::iterator iter = gArmsDealersInventory[ubArmsDealer].begin();
iter != gArmsDealersInventory[ubArmsDealer].end(); ++iter) {
if ( iter->ItemIsInInventory() == true )
{
if( iter->IsUnderRepair() == true )
{
// if this item will be done later than the latest we've found so far
if ( iter->uiRepairDoneTime > uiWhenFree )
{
// then we're busy til then!
uiWhenFree = iter->uiRepairDoneTime;
}
}
}
}
return( uiWhenFree );
}
UINT32 CalculateObjectItemRepairTime( UINT8 ubArmsDealer, OBJECTTYPE *pItemObject )
{
UINT32 uiRepairTime = 0;
for (int x = 0; x < pItemObject->ubNumberOfObjects; ++x) {
uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, pItemObject->usItem, (*pItemObject)[x]->data.objectStatus );
// add time to repair any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable
if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
{
//uiRepairTime += CalculateSimpleItemRepairTime( ubArmsDealer, iter->usItem, (*iter)[x]->data.objectStatus );
uiRepairTime += CalculateObjectItemRepairTime( ubArmsDealer, &(*iter) );
}
}
}
return( uiRepairTime );
}
UINT32 CalculateSimpleItemRepairTime( UINT8 ubArmsDealer, UINT16 usItemIndex, INT16 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 CalculateObjectItemRepairCost( UINT8 ubArmsDealer, OBJECTTYPE *pItemObject )
{
UINT32 uiRepairCost = 0;
for (int x = 0; x < pItemObject->ubNumberOfObjects; ++x) {
uiRepairCost = CalculateSimpleItemRepairCost( ubArmsDealer, pItemObject->usItem, (*pItemObject)[x]->data.objectStatus );
// add cost of repairing any attachments on it
for (attachmentList::iterator iter = (*pItemObject)[x]->attachments.begin(); iter != (*pItemObject)[x]->attachments.end(); ++iter) {
// if damaged and repairable
if ( ( (*iter)[x]->data.objectStatus < 100 ) && CanDealerRepairItem( ubArmsDealer, iter->usItem ) )
{
uiRepairCost += CalculateObjectItemRepairCost( ubArmsDealer, &(*iter));
}
}
}
return( uiRepairCost );
}
UINT32 CalculateSimpleItemRepairCost( UINT8 ubArmsDealer, UINT16 usItemIndex, INT16 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 );
}
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 );
}
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 );
}