/*****************************************************************************
* DikuMUD (C) 1990, 1991 by: *
* Sebastian Hammer, Michael Seifert, Hans Henrik Staefeldt, Tom Madsen, *
* and Katja Nyboe. *
*---------------------------------------------------------------------------*
* MERC 2.1 (C) 1992, 1993 by: *
* Michael Chastain, Michael Quan, and Mitchell Tse. *
*---------------------------------------------------------------------------*
* SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by: Derek Snider. *
* Team: Thoric, Altrag, Blodkai, Narn, Haus, Scryn, Rennard, Swordbearer, *
* gorog, Grishnakh, Nivek, Tricops, and Fireblade. *
*---------------------------------------------------------------------------*
* SMAUG 1.7 FUSS by: Samson and others of the SMAUG community. *
* Their contributions are greatly appreciated. *
*---------------------------------------------------------------------------*
* LoP (C) 2006, 2007, 2008 by: the LoP team. *
*---------------------------------------------------------------------------*
* Bank Support *
*****************************************************************************/
/*
* Can create/delete an account.
* Can withdraw/deposit into an account.
* Can transfer gold from your account to another.
* Accounts can have other characters share it.
* Memory Cleaned up on exit.
* Bank data is saved and loaded.
*/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "h/mud.h"
#include "h/sha256.h"
#include "h/bank.h"
bool check_parse_name( char *name, bool newchar );
BANK_DATA *first_bank, *last_bank;
#define GCHANGE 1000000
bool has_gold( CHAR_DATA *ch, int amount )
{
int cgold, cmillion;
int check = amount;
int millions = 0;
if( !ch )
return false;
cmillion = ch->mgold;
cgold = ch->gold;
while( check >= GCHANGE )
{
millions++;
check -= GCHANGE;
}
while( cgold < check )
{
cmillion--;
cgold += GCHANGE;
}
if( millions > cmillion )
return false;
if( check > cgold )
return false;
return true;
}
bool has_balance( BANK_DATA *bank, int amount )
{
int cgold, cmillion;
int check = amount;
int millions = 0;
if( !bank )
return false;
cmillion = bank->mbalance;
cgold = bank->balance;
while( check >= GCHANGE )
{
millions++;
check -= GCHANGE;
}
while( cgold < check )
{
cmillion--;
cgold += GCHANGE;
}
if( millions > cmillion )
return false;
if( check > cgold )
return false;
return true;
}
void set_gold( CHAR_DATA *ch, int amount )
{
int uamt = amount;
if( !ch )
return;
ch->mgold = 0;
ch->gold = 0;
while( uamt >= GCHANGE )
{
ch->mgold++;
uamt -= GCHANGE;
}
ch->gold += uamt;
while( ch->gold >= GCHANGE )
{
ch->mgold++;
ch->gold -= GCHANGE;
}
}
void increase_gold( CHAR_DATA *ch, int amount )
{
int uamt = amount;
if( !ch )
return;
if( ch->mgold >= 2000000000 )
{
send_to_char( "You already have as much gold as you can handle.\r\n", ch );
return;
}
while( uamt >= GCHANGE )
{
if( ++ch->mgold >= 2000000000 )
{
send_to_char( "You're now carrying as much gold as you can handle.\r\n", ch );
ch->gold = 0;
return;
}
uamt -= GCHANGE;
}
ch->gold += uamt;
while( ch->gold >= GCHANGE )
{
if( ++ch->mgold >= 2000000000 )
{
send_to_char( "You're now carrying as much gold as you can handle.\r\n", ch );
ch->gold = 0;
return;
}
ch->gold -= GCHANGE;
}
}
void increase_balance( CHAR_DATA *ch, BANK_DATA *bank, int amount )
{
int uamt = amount;
if( !bank )
return;
if( bank->mbalance >= 2000000000 )
{
if( ch )
send_to_char( "The account already has as much gold as it can handle.\r\n", ch );
return;
}
while( uamt >= GCHANGE )
{
if( ++bank->mbalance >= 2000000000 )
{
if( ch )
send_to_char( "The account now has as much gold as it can handle.\r\n", ch );
bank->balance = 0;
return;
}
uamt -= GCHANGE;
}
bank->balance += uamt;
while( bank->balance >= GCHANGE )
{
if( ++bank->mbalance >= 2000000000 )
{
if( ch )
send_to_char( "The account now has as much gold as it can handle.\r\n", ch );
bank->balance = 0;
return;
}
bank->balance -= GCHANGE;
}
}
void decrease_gold( CHAR_DATA *ch, int amount )
{
int uamt = amount;
if( !ch )
return;
while( uamt >= GCHANGE )
{
ch->mgold--;
uamt -= GCHANGE;
}
while( ch->gold < uamt )
{
ch->mgold--;
ch->gold += GCHANGE;
}
ch->gold -= uamt;
if( ch->mgold < 0 )
ch->mgold = 0;
if( ch->gold < 0 )
ch->gold = 0;
}
void decrease_balance( BANK_DATA *bank, int amount )
{
int uamt = amount;
if( !bank )
return;
while( uamt >= GCHANGE )
{
bank->mbalance--;
uamt -= GCHANGE;
}
while( bank->balance < uamt )
{
bank->mbalance--;
bank->balance += GCHANGE;
}
bank->balance -= uamt;
if( bank->mbalance < 0 )
bank->mbalance = 0;
if( bank->balance < 0 )
bank->balance = 0;
}
/* This is set up to show as first is in millions and amount has the rest */
char *show_big_nums( int millions, int amount )
{
int index_new, rest, x;
unsigned int nindex;
char buf[32];
static char buf_new[32];
if( millions > 0 )
snprintf( buf, sizeof( buf ), "%d%6.6d", millions, amount );
else
snprintf( buf, sizeof( buf ), "%d", amount );
rest = strlen( buf ) % 3;
for( nindex = index_new = 0; nindex < strlen( buf ); nindex++, index_new++ )
{
x = nindex - rest;
if( nindex != 0 && ( x % 3 ) == 0 )
buf_new[index_new++] = ',';
buf_new[index_new] = buf[nindex];
}
buf_new[index_new] = '\0';
return buf_new;
}
void free_shared( SHARE_DATA *shared )
{
STRFREE( shared->name );
DISPOSE( shared );
}
void free_transaction( TRANSACTION_DATA *trans )
{
STRFREE( trans->transaction );
DISPOSE( trans );
}
void remove_shared( BANK_DATA *bank, SHARE_DATA *shared )
{
UNLINK( shared, bank->first_share, bank->last_share, next, prev );
}
void remove_transaction( BANK_DATA *bank, TRANSACTION_DATA *trans )
{
UNLINK( trans, bank->first_transaction, bank->last_transaction, next, prev );
}
void free_all_shared( BANK_DATA *bank )
{
SHARE_DATA *shared, *shared_next;
for( shared = bank->first_share; shared; shared = shared_next )
{
shared_next = shared->next;
remove_shared( bank, shared );
free_shared( shared );
}
}
void free_all_transactions( BANK_DATA *bank )
{
TRANSACTION_DATA *trans, *trans_next;
for( trans = bank->first_transaction; trans; trans = trans_next )
{
trans_next = trans->next;
remove_transaction( bank, trans );
free_transaction( trans );
}
}
void add_shared( BANK_DATA *bank, SHARE_DATA *shared )
{
LINK( shared, bank->first_share, bank->last_share, next, prev );
}
void add_transaction( BANK_DATA *bank, TRANSACTION_DATA *trans )
{
LINK( trans, bank->first_transaction, bank->last_transaction, next, prev );
}
void free_bank( BANK_DATA *bank )
{
STRFREE( bank->account );
STRFREE( bank->created );
free_all_shared( bank );
free_all_transactions( bank );
DISPOSE( bank );
}
void remove_bank( BANK_DATA *bank )
{
UNLINK( bank, first_bank, last_bank, next, prev );
}
void free_all_banks( void )
{
BANK_DATA *bank, *bank_next;
for( bank = first_bank; bank; bank = bank_next )
{
bank_next = bank->next;
remove_bank( bank );
free_bank( bank );
}
}
void add_bank( BANK_DATA *bank )
{
LINK( bank, first_bank, last_bank, next, prev );
}
void save_banks( void )
{
BANK_DATA *bank;
SHARE_DATA *shared;
TRANSACTION_DATA *trans;
FILE *fp;
if( !first_bank )
{
remove_file( BANK_FILE );
return;
}
if( !( fp = fopen( BANK_FILE, "w" ) ) )
{
bug( "%s: Can't open %s for writing.", __FUNCTION__, BANK_FILE );
perror( BANK_FILE );
return;
}
for( bank = first_bank; bank; bank = bank->next )
{
fprintf( fp, "%s", "#BANK\n" );
fprintf( fp, "Createdby %s~\n", bank->created );
fprintf( fp, "Account %s~\n", bank->account );
fprintf( fp, "MBalance %d\n", bank->mbalance );
fprintf( fp, "Balance %d\n", bank->balance );
for( shared = bank->first_share; shared; shared = shared->next )
if( shared->name )
fprintf( fp, "Shared %s~\n", shared->name );
for( trans = bank->first_transaction; trans; trans = trans->next )
if( trans->transaction )
fprintf( fp, "Trans %s~\n", trans->transaction );
fprintf( fp, "%s", "End\n\n" );
}
fprintf( fp, "%s", "#END\n" );
fclose( fp );
fp = NULL;
}
BANK_DATA *new_bank( void )
{
BANK_DATA *bank = NULL;
CREATE( bank, BANK_DATA, 1 );
if( !bank )
{
bug( "%s: bank is NULL after CREATE.", __FUNCTION__ );
return NULL;
}
bank->created = NULL;
bank->account = NULL;
bank->first_share = bank->last_share = NULL;
bank->first_transaction = bank->last_transaction = NULL;
bank->mbalance = 0;
bank->balance = 0;
return bank;
}
void fread_bank( FILE *fp )
{
const char *word;
bool fMatch;
BANK_DATA *bank;
bank = new_bank( );
for( ;; )
{
word = feof( fp ) ? "End" : fread_word( fp );
fMatch = false;
switch( UPPER( word[0] ) )
{
case '*':
fMatch = true;
fread_to_eol( fp );
break;
case 'E':
if( !str_cmp( word, "End" ) )
{
add_bank( bank );
return;
}
break;
case 'A':
KEY( "Account", bank->account, fread_string( fp ) );
break;
case 'B':
KEY( "Balance", bank->balance, fread_number( fp ) );
break;
case 'C':
KEY( "Createdby", bank->created, fread_string( fp ) );
break;
case 'M':
KEY( "MBalance", bank->mbalance, fread_number( fp ) );
break;
case 'S':
if( !str_cmp( word, "Shared" ) )
{
SHARE_DATA *shared;
CREATE( shared, SHARE_DATA, 1 );
shared->name = fread_string( fp );
add_shared( bank, shared );
fMatch = true;
break;
}
break;
case 'T':
if( !str_cmp( word, "Trans" ) )
{
TRANSACTION_DATA *trans;
CREATE( trans, TRANSACTION_DATA, 1 );
trans->transaction = fread_string( fp );
add_transaction( bank, trans );
fMatch = true;
break;
}
break;
}
if( !fMatch )
{
bug( "%s: no match: %s", __FUNCTION__, word );
fread_to_eol( fp );
}
}
free_bank( bank );
}
void load_banks( void )
{
FILE *fp;
first_bank = last_bank = NULL;
if( !( fp = fopen( BANK_FILE, "r" ) ) )
return;
for( ;; )
{
char letter;
char *word;
letter = fread_letter( fp );
if( letter == '*' )
{
fread_to_eol( fp );
continue;
}
if( letter != '#' )
{
bug( "%s: # not found.", __FUNCTION__ );
break;
}
word = fread_word( fp );
if( !str_cmp( word, "BANK" ) )
{
fread_bank( fp );
continue;
}
else if( !str_cmp( word, "END" ) )
break;
else
{
bug( "%s: bad section (%s).", __FUNCTION__, word );
fread_to_eol( fp );
continue;
}
}
fclose( fp );
fp = NULL;
}
typedef enum
{
BANK_DELETE, BANK_SHARE, BANK_TRANSFER, BANK_BALANCE, BANK_WITHDRAW, BANK_DEPOSIT, BANK_CREATE
} bank_types;
BANK_DATA *find_bank( char *account )
{
BANK_DATA *bank;
for( bank = first_bank; bank; bank = bank->next )
{
if( !str_cmp( bank->account, account ) )
return bank;
}
return NULL;
}
CHAR_DATA *find_banker( CHAR_DATA *ch )
{
CHAR_DATA *banker = NULL;
for( banker = ch->in_room->first_person; banker; banker = banker->next_in_room )
{
if( is_npc( banker ) && xIS_SET( banker->act, ACT_BANKER ) )
break;
}
return banker;
}
bool can_access_bank( BANK_DATA *bank, CHAR_DATA *ch )
{
SHARE_DATA *shared;
/* Creater of the account naturally has access */
if( !str_cmp( bank->created, ch->name ) )
return true;
for( shared = bank->first_share; shared; shared = shared->next )
{
/* If they are in the shared then they have access */
if( !str_cmp( shared->name, ch->name ) )
return true;
}
/* Well made it this far so naturally they can't access it */
return false;
}
void modify_shared( CHAR_DATA *ch, BANK_DATA *bank, char *argument )
{
SHARE_DATA *shared;
char arg[MIL];
bool adding, save = false;
/* See if they have access to it already and they aren't the leader */
while( argument && argument[0] != '\0' )
{
adding = true; /* Lets assume we will be adding at first */
argument = one_argument( argument, arg );
/* If the arg is the creator just ignore it so it doesn't get put in as shared */
if( !str_cmp( bank->created, arg ) )
continue;
for( shared = bank->first_share; shared; shared = shared->next )
{
/* Ok it was in the list so lets remove it */
if( !str_cmp( shared->name, arg ) )
{
remove_shared( bank, shared );
free_shared( shared );
adding = false;
save = true;
ch_printf( ch, "%s is no longer able to access the account.\r\n", arg );
break; /* Break out the for loop */
}
}
/* Didn't find one so lets add it */
if( adding )
{
/* Check for a valid pfile */
if( !valid_pfile( arg ) )
{
ch_printf( ch, "%s is not a valid player and can't be added.\r\n", arg );
continue;
}
CREATE( shared, SHARE_DATA, 1 );
shared->name = STRALLOC( arg );
add_shared( bank, shared );
save = true;
ch_printf( ch, "%s is now able to access the account.\r\n", arg );
}
}
if( save )
save_banks( );
else
send_to_char( "Nothing changed in the account.\r\n", ch );
}
void new_transaction( BANK_DATA *bank, char *string )
{
TRANSACTION_DATA *trans;
struct timeval now_time;
int count = 0;
char buf[MSL];
/* Update time. */
gettimeofday( &now_time, NULL );
current_time = ( time_t ) now_time.tv_sec;
current_time += ( time_t ) TIME_MODIFY;
snprintf( buf, sizeof( buf ), "%s %s", distime( current_time ), string );
CREATE( trans, TRANSACTION_DATA, 1 );
STRSET( trans->transaction, buf );
add_transaction( bank, trans );
for( trans = bank->first_transaction; trans; trans = trans->next )
++count;
while( count > 20 )
{
trans = bank->first_transaction;
remove_transaction( bank, trans );
free_transaction( trans );
--count;
}
}
void give_interest( void )
{
BANK_DATA *bank = NULL;
char buf[MSL];
int interest = 0;
for( bank = first_bank; bank; bank = bank->next )
{
/* Interest on 1 million gold is 300 coins so use it instead of .0003 */
if( bank->mbalance > 0 )
interest += ( int )( bank->mbalance * 300 );
if( bank->balance > 0 )
interest += ( int )( bank->balance * .0003 );
if( interest > 0 )
{
snprintf( buf, sizeof( buf ), "Gained %s interest.", num_punct( interest ) );
new_transaction( bank, buf );
increase_balance( NULL, bank, interest );
}
}
save_banks( );
}
void handle_bank( CHAR_DATA *ch, char *account, short type, int amount, char *taccount )
{
CHAR_DATA *banker = NULL;
BANK_DATA *bank = NULL, *tbank = NULL;
TRANSACTION_DATA *trans;
char buf[MSL];
if( !( banker = find_banker( ch ) ) )
{
send_to_char( "You aren't at a banker.\r\n", ch );
return;
}
/* Other wise we should be doing something to balance */
if( type != BANK_SHARE && type != BANK_DELETE && type != BANK_BALANCE && amount <= 0 )
{
send_to_char( "You need to use a number above 0.\r\n", ch );
return;
}
if( ( bank = find_bank( account ) ) && type == BANK_CREATE )
{
send_to_char( "An account by that name already exist.\r\n", ch );
return;
}
if( !bank && type != BANK_CREATE )
{
send_to_char( "Invalid account.\r\n", ch );
return;
}
/* Create a new account with amount deposit */
if( type == BANK_CREATE )
{
if( amount <= 0 )
{
send_to_char( "You need to at least deposit something to open an account.\r\n", ch );
return;
}
if( amount > 2000000000 )
{
send_to_char( "You can't deposit more then 2 billion coins at once.\r\n", ch );
return;
}
if( !( bank = new_bank( ) ) )
return;
bank->created = STRALLOC( ch->name );
bank->account = STRALLOC( account );
increase_balance( ch, bank, amount );
decrease_gold( ch, amount );
snprintf( buf, sizeof( buf ), "%s opened account with %d coins.", ch->name, amount );
new_transaction( bank, buf );
add_bank( bank );
save_banks( );
save_char_obj( ch );
ch_printf( ch, "Account %s has been created and has a balance of %s.\r\n", bank->account,
show_big_nums( bank->mbalance, bank->balance ) );
return;
}
/* Do they have access to this account? */
if( !can_access_bank( bank, ch ) )
{
send_to_char( "You don't have permission to do anything with that account.\r\n", ch );
return;
}
/* Modify who all else can access this account */
if( type == BANK_SHARE )
{
modify_shared( ch, bank, taccount );
return;
}
/* Check balance */
if( type == BANK_BALANCE )
{
ch_printf( ch, "You have %s gold in that account.\r\n", show_big_nums( bank->mbalance, bank->balance ) );
for( trans = bank->first_transaction; trans; trans = trans->next )
{
if( trans == bank->first_transaction )
send_to_char( "Last 20 Transactions.\r\n", ch );
ch_printf( ch, "%s\r\n", trans->transaction );
}
return;
}
/* Delete account */
if( type == BANK_DELETE )
{
remove_bank( bank );
free_bank( bank );
save_banks( );
send_to_char( "The account has been deleted.\r\n", ch );
return;
}
/* Withdraw amount */
if( type == BANK_WITHDRAW )
{
if( !has_balance( bank, amount ) )
{
send_to_char( "You don't have that much to withdraw.\r\n", ch );
return;
}
if( amount <= 0 )
{
send_to_char( "You can't withdraw 0 or less coins.\r\n", ch );
return;
}
if( amount > 2000000000 )
{
send_to_char( "You can't withdraw more then 2 billion coins at once.\r\n", ch );
return;
}
snprintf( buf, sizeof( buf ), "%s withdrawled %d coins.", ch->name, amount );
new_transaction( bank, buf );
decrease_balance( bank, amount );
increase_gold( ch, amount );
ch_printf( ch, "You have withdrawn %d gold ", amount );
ch_printf( ch, "and now have %s on that account.\r\n", show_big_nums( bank->mbalance, bank->balance ) );
if( bank->balance <= 0 )
{
remove_bank( bank );
free_bank( bank );
send_to_char( "As you get the last of your gold from the bank, the account is closed.\r\n", ch );
}
save_banks( );
save_char_obj( ch );
return;
}
/* Transfer amount */
if( type == BANK_TRANSFER )
{
if( !has_balance( bank, amount ) )
{
send_to_char( "You don't have that much in that account.\r\n", ch );
return;
}
if( amount <= 0 )
{
send_to_char( "You can't transfer 0 or less coins.\r\n", ch );
return;
}
if( amount > 2000000000 )
{
send_to_char( "You can't transfer more then 2 billion coins at once.\r\n", ch );
return;
}
if( !( tbank = find_bank( taccount ) ) )
{
send_to_char( "Invalid account to transfer funds to.\r\n", ch );
return;
}
snprintf( buf, sizeof( buf ), "%s transfered %d coins from %s.", ch->name, amount, bank->account );
new_transaction( tbank, buf );
snprintf( buf, sizeof( buf ), "%s transfered %d coins to %s.", ch->name, amount, tbank->account );
new_transaction( bank, buf );
increase_balance( ch, tbank, amount );
decrease_balance( bank, amount );
if( bank->balance <= 0 )
{
remove_bank( bank );
free_bank( bank );
send_to_char( "As you transfer the last of your gold from the bank, the account is closed.\r\n", ch );
}
save_banks( );
ch_printf( ch, "You have transfered %d gold to another account.\r\n", amount );
return;
}
/* Deposit amount */
if( type == BANK_DEPOSIT )
{
if( !has_gold( ch, amount ) )
{
send_to_char( "You can't deposit more gold then you have.\r\n", ch );
return;
}
if( amount <= 0 )
{
send_to_char( "You can't deposit 0 or less coins.\r\n", ch );
return;
}
if( amount > 2000000000 )
{
send_to_char( "You can't deposit more then 2 billion coins at once.\r\n", ch );
return;
}
snprintf( buf, sizeof( buf ), "%s deposited %d coins.", ch->name, amount );
new_transaction( bank, buf );
increase_balance( ch, bank, amount );
decrease_gold( ch, amount );
save_banks( );
save_char_obj( ch );
ch_printf( ch, "You have deposited %d gold and now have %s on that account.\r\n",
amount, show_big_nums( bank->mbalance, bank->balance ) );
return;
}
send_to_char( "Ok, now exactly how did you get here???\r\n", ch );
}
void bank_Usage_display( CHAR_DATA *ch )
{
send_to_char( "Usage: bank <account> delete/balance\r\n", ch );
send_to_char( "Usage: bank <account> share <name>\r\n", ch );
send_to_char( "Usage: bank <account> create/deposit/withdraw <amount>.\r\n", ch );
send_to_char( "Usage: bank <account> transfer <amount> <account>\r\n", ch );
}
CMDF( do_bank )
{
CHAR_DATA *banker = NULL;
char arg[MIL], account[MIL];
short type;
int amount = 0;
/* Show immortals above and equal to a certian level all bank accounts and amounts? */
if( get_trust( ch ) >= PERM_HEAD && ( !argument || argument[0] == '\0' ) )
{
BANK_DATA *bank;
int count = 0;
for( bank = first_bank; bank; bank = bank->next )
{
amount++;
ch_printf( ch, "&[white]%15.15s &[yellow]%19s&D", bank->account, show_big_nums( bank->mbalance, bank->balance ) );
if( ++count == 3 )
{
count = 0;
send_to_char( "\r\n", ch );
}
else
send_to_char( " ", ch );
}
if( count != 0 )
send_to_char( "\r\n", ch );
if( amount == 0 )
send_to_char( "&[white]No accounts found to display.&D\r\n", ch );
else
ch_printf( ch, "&[lblue]%d &[white]accounts.&D\r\n", amount );
return;
}
if( !( banker = find_banker( ch ) ) )
{
send_to_char( "You aren't at a banker.\r\n", ch );
return;
}
argument = one_argument( argument, account );
if( account == NULL || account[0] == '\0' )
{
bank_Usage_display( ch );
return;
}
argument = one_argument( argument, arg );
if( !str_cmp( arg, "create" ) ) type = BANK_CREATE;
else if( !str_cmp( arg, "balance" ) ) type = BANK_BALANCE;
else if( !str_cmp( arg, "deposit" ) ) type = BANK_DEPOSIT;
else if( !str_cmp( arg, "withdraw" ) ) type = BANK_WITHDRAW;
else if( !str_cmp( arg, "transfer" ) ) type = BANK_TRANSFER;
else if( !str_cmp( arg, "delete" ) ) type = BANK_DELETE;
else if( !str_cmp( arg, "share" ) ) type = BANK_SHARE;
else
{
bank_Usage_display( ch );
return;
}
if( type != BANK_SHARE && type != BANK_DELETE && type != BANK_BALANCE )
{
argument = one_argument( argument, arg );
amount = atoi( arg );
}
if( type != BANK_SHARE && type != BANK_DELETE && type != BANK_BALANCE && amount <= 0 )
{
bank_Usage_display( ch );
return;
}
handle_bank( ch, account, type, amount, argument );
}