2018-10-24 14:19:48 +00:00
/*
* Irresistible Gaming (c) 2018
* Developed by Lorenc Pekaj
2018-11-01 06:48:25 +00:00
* Module: cnr\features\stocks\stocks.pwn
* Purpose: stock market system for players
2018-10-24 14:19:48 +00:00
*/
/* ** Includes ** */
#include < YSI\y_hooks >
/* ** Definitions ** */
2018-11-01 22:57:08 +00:00
#define MAX_STOCKS ( 16 )
2018-10-24 21:32:18 +00:00
2018-11-01 06:48:25 +00:00
#define STOCK_REPORTING_PERIOD ( 86400 ) // 1 day
2018-10-24 21:32:18 +00:00
#define STOCK_REPORTING_PERIODS ( 30 ) // last 30 periods (days)
#define DIALOG_STOCK_MARKET 8923
2018-10-31 07:19:18 +00:00
#define DIALOG_PLAYER_STOCKS 8924
2018-10-26 00:44:45 +00:00
#define DIALOG_STOCK_MARKET_BUY 8925
2018-10-31 07:19:18 +00:00
#define DIALOG_STOCK_MARKET_SELL 8926
2018-10-26 00:44:45 +00:00
#define STOCK_MM_USER_ID ( 0 )
2018-10-24 14:19:48 +00:00
2018-11-01 22:57:08 +00:00
/* ** Constants ** */
static const Float: STOCK_MARKET_POOL_FACTOR = 100000.0; // for every STOCK_MARKET_POOL_FACTOR ... STOCK_MARKET_PRICE_FACTOR added to the price
2018-11-03 05:06:58 +00:00
static const Float: STOCK_MARKET_PRICE_FACTOR = 3.0; // for every STOCK_MARKET_POOL_FACTOR ... STOCK_MARKET_PRICE_FACTOR added to the price
//static const Float: STOCK_MARKET_PRICE_FLOOR = 20.0; // the price in which the price will always be above or equal to
2018-11-01 22:57:08 +00:00
static const Float: STOCK_MARKET_TRADING_FEE = 0.01; // trading fee (buy/sell) percentage as decimal
static const Float: STOCK_DEFAULT_START_POOL = 0.0; // the default amount that the pool is set to upon a new report
static const Float: STOCK_DEFAULT_START_PRICE = 0.0; // the default starting price for a new report (useless for now)
2018-10-24 14:19:48 +00:00
/* ** Variables ** */
2018-10-24 21:32:18 +00:00
enum E_STOCK_MARKET_DATA
{
2018-10-31 07:19:18 +00:00
E_NAME[ 64 ], E_SYMBOL[ 4 ], Float: E_MAX_SHARES,
2018-10-24 21:32:18 +00:00
// market maker
2018-10-31 07:19:18 +00:00
Float: E_IPO_SHARES, Float: E_IPO_PRICE, Float: E_MAX_PRICE
2018-10-24 21:32:18 +00:00
};
enum E_STOCK_MARKET_PRICE_DATA
{
2018-10-31 07:19:18 +00:00
E_SQL_ID, Float: E_PRICE, Float: E_POOL
2018-10-24 21:32:18 +00:00
};
2018-10-28 11:34:48 +00:00
enum
{
2018-11-01 10:55:21 +00:00
E_STOCK_MINING_COMPANY,
2018-11-03 05:06:58 +00:00
E_STOCK_AMMUNATION,
E_STOCK_VEHICLE_DEALERSHIP,
E_STOCK_SUPA_SAVE,
E_STOCK_TRUCKING_COMPANY,
2018-11-03 05:21:56 +00:00
E_STOCK_CLUCKIN_BELL,
2018-11-04 23:23:38 +00:00
E_STOCK_PAWN_STORE,
E_STOCK_CASINO,
E_STOCK_GOVERNMENT
2018-10-28 11:34:48 +00:00
};
2018-10-24 21:32:18 +00:00
static stock
g_stockMarketData [ MAX_STOCKS ] [ E_STOCK_MARKET_DATA ],
2018-10-30 22:42:10 +00:00
g_stockMarketReportData [ MAX_STOCKS ] [ STOCK_REPORTING_PERIODS ] [ E_STOCK_MARKET_PRICE_DATA ],
2018-10-31 07:19:18 +00:00
Iterator: stockmarkets < MAX_STOCKS >,
Float: p_PlayerShares [ MAX_PLAYERS ] [ MAX_STOCKS ]
2018-10-24 21:32:18 +00:00
;
2018-10-24 14:19:48 +00:00
/* ** Hooks ** */
2018-10-24 21:32:18 +00:00
hook OnScriptInit( )
{
2018-10-25 17:58:12 +00:00
// server variables
AddServerVariable( "stock_report_time", "0", GLOBAL_VARTYPE_INT );
2018-10-31 08:01:35 +00:00
AddServerVariable( "stock_trading_fees", "0.0", GLOBAL_VARTYPE_FLOAT );
2018-10-25 17:58:12 +00:00
// create markets
2018-11-03 05:06:58 +00:00
CreateStockMarket( E_STOCK_MINING_COMPANY, "The Mining Company", "MC", 100000.0, 25.0, 500.0 ); // 50m mcap max
2018-11-01 10:55:21 +00:00
CreateStockMarket( E_STOCK_AMMUNATION, "Ammu-Nation", "A", 100000.0, 25.0, 250.0 ); // 25m mcap max
2018-11-03 05:06:58 +00:00
CreateStockMarket( E_STOCK_VEHICLE_DEALERSHIP, "Vehicle Dealership", "VD", 100000.0, 100.0, 250.0 ); // 25m mcap max
2018-11-03 05:21:56 +00:00
CreateStockMarket( E_STOCK_SUPA_SAVE, "Supa-Save", "SS", 100000.0, 25.0, 250.0 ); // 25m mcap max
2018-11-03 05:06:58 +00:00
CreateStockMarket( E_STOCK_TRUCKING_COMPANY, "The Trucking Company", "TC", 100000.0, 50.0, 250.0 ); // 25m mcap max
CreateStockMarket( E_STOCK_CLUCKIN_BELL, "Cluckin' Bell", "CB", 100000.0, 50.0, 250.0 ); // 25m mcap max
2018-11-03 05:21:56 +00:00
CreateStockMarket( E_STOCK_PAWN_STORE, "Pawn Store", "PS", 100000.0, 50.0, 250.0 ); // 25m mcap max
2018-11-04 23:23:38 +00:00
CreateStockMarket( E_STOCK_CASINO, "Casino", "CAS", 100000.0, 990.0, 5000.0 ); // 500m mcap max
CreateStockMarket( E_STOCK_GOVERNMENT, "Government", "GOV", 100000.0, 750.0, 5000.0 ); // 500m mcap max
2018-10-25 17:58:12 +00:00
return 1;
}
hook OnServerUpdate( )
{
new current_time = GetServerTime( );
new last_reporting = GetServerVariableInt( "stock_report_time" );
// check if its reporting time
if ( current_time > last_reporting )
{
// reporting period
UpdateServerVariableInt( "stock_report_time", current_time + STOCK_REPORTING_PERIOD );
// create a new reporting period for every stock there
foreach ( new s : stockmarkets )
{
2018-10-30 22:42:10 +00:00
StockMarket_ReleaseDividends( s );
2018-10-25 17:58:12 +00:00
}
print( "Successfully created new reporting period for all online companies" );
}
2018-10-24 21:32:18 +00:00
return 1;
}
2018-11-01 22:57:08 +00:00
hook OnPlayerDisconnect( playerid, reason )
{
for ( new i = 0; i < sizeof( p_PlayerShares[ ] ); i ++ ) {
p_PlayerShares[ playerid ] [ i ] = 0.0;
}
return 1;
}
2018-10-26 00:44:45 +00:00
hook OnDialogResponse( playerid, dialogid, response, listitem, inputtext[ ] )
{
if ( dialogid == DIALOG_STOCK_MARKET && response )
{
new
x = 0;
foreach ( new s : stockmarkets )
{
if ( x == listitem )
{
SetPVarInt( playerid, "stockmarket_selection", s );
StockMarket_ShowBuySlip( playerid, s );
2018-10-31 07:19:18 +00:00
break;
}
x ++;
}
return 1;
}
else if ( dialogid == DIALOG_PLAYER_STOCKS && response )
{
new
x = 0;
foreach ( new stockid : stockmarkets ) if ( p_PlayerShares[ playerid ] [ stockid ] )
{
if ( x == listitem )
{
SetPVarInt( playerid, "stockmarket_selling_stock", stockid );
StockMarket_ShowSellSlip( playerid, stockid );
break;
2018-10-26 00:44:45 +00:00
}
x ++;
}
return 1;
}
2018-10-31 07:19:18 +00:00
else if ( dialogid == DIALOG_STOCK_MARKET_SELL )
{
if ( ! response ) {
return cmd_shares( playerid, "" ), 1;
}
new
stockid = GetPVarInt( playerid, "stockmarket_selling_stock" );
if ( ! Iter_Contains( stockmarkets, stockid ) ) {
return SendError( playerid, "There was an error processing your sell order, please try again." );
}
new
input_shares;
if ( sscanf( inputtext, "d", input_shares ) ) SendError( playerid, "You must use a valid value." );
else if ( input_shares > floatround( p_PlayerShares[ playerid ] [ stockid ], floatround_floor ) ) SendError( playerid, "You do not have this many shares available to sell." );
2018-11-01 06:48:25 +00:00
else if ( input_shares < 1 ) SendError( playerid, "The minimum number of shares you can sell is 1." );
2018-10-31 07:19:18 +00:00
else
{
new
Float: shares = float( input_shares );
if ( ( p_PlayerShares[ playerid ] [ stockid ] -= shares ) < 0.1 ) {
mysql_single_query( sprintf( "DELETE FROM `STOCK_OWNERS` WHERE `USER_ID`=%d AND `STOCK_ID`=%d", GetPlayerAccountID( playerid ), stockid ) );
} else {
StockMarket_GiveShares( stockid, GetPlayerAccountID( playerid ), -shares );
}
StockMarket_UpdateSellOrder( stockid, GetPlayerAccountID( playerid ), shares );
2018-10-31 08:01:35 +00:00
SendServerMessage( playerid, "You have placed a sell order for %s shares at %s each. "COL_ORANGE"To cancel your sell order, /shares cancel", number_format( shares, .decimals = 2 ), cash_format( g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ], .decimals = 2 ) );
2018-10-31 07:19:18 +00:00
return 1;
}
return StockMarket_ShowSellSlip( playerid, stockid );
}
2018-10-26 00:44:45 +00:00
else if ( dialogid == DIALOG_STOCK_MARKET_BUY )
{
new
stockid = GetPVarInt( playerid, "stockmarket_selection" );
if ( response )
{
new
2018-10-31 07:19:18 +00:00
shares;
2018-10-26 00:44:45 +00:00
2018-10-31 07:19:18 +00:00
if ( sscanf( inputtext, "d", shares ) ) SendError( playerid, "You must use a valid value." );
2018-11-03 05:06:58 +00:00
else if ( shares < 1 ) SendError( playerid, "The minimum number of shares you can buy is 1." );
2018-10-26 00:44:45 +00:00
else
{
2018-10-31 07:19:18 +00:00
mysql_tquery( dbHandle, sprintf( "SELECT * FROM `STOCK_SELL_ORDERS` WHERE `STOCK_ID`=%d ORDER BY `LIST_DATE` ASC", stockid ), "StockMarket_OnPurchaseOrder", "ddf", playerid, stockid, float( shares ) );
2018-10-26 00:44:45 +00:00
return 1;
}
return StockMarket_ShowBuySlip( playerid, stockid );
}
else
{
return cmd_stockmarkets( playerid, "" );
}
}
return 1;
}
2018-10-24 21:32:18 +00:00
/* ** SQL Thread ** */
thread Stock_UpdateReportingPeriods( stockid )
{
new
rows = cache_get_row_count( );
if ( rows )
{
for ( new row = 0; row < rows; row ++ )
{
2018-10-30 22:42:10 +00:00
g_stockMarketReportData[ stockid ] [ row ] [ E_SQL_ID ] = cache_get_field_content_int( row, "ID" );
g_stockMarketReportData[ stockid ] [ row ] [ E_POOL ] = cache_get_field_content_float( row, "POOL" );
2018-10-31 07:19:18 +00:00
g_stockMarketReportData[ stockid ] [ row ] [ E_PRICE ] = cache_get_field_content_float( row, "PRICE" );
2018-10-24 21:32:18 +00:00
}
}
2018-10-26 00:44:45 +00:00
else // no historical reporting data, restock the market maker
{
// set current stock market prices to IPO
2018-11-01 22:57:08 +00:00
g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ] = g_stockMarketData[ stockid ] [ E_IPO_PRICE ];
2018-10-26 00:44:45 +00:00
2018-10-31 08:04:59 +00:00
// create 2 reports for the company using the IPO price ... this way the price is not $0
2018-11-01 22:57:08 +00:00
for ( new i = 0; i < 3; i ++ ) {
2018-10-31 08:04:59 +00:00
StockMarket_ReleaseDividends( stockid );
}
2018-10-26 00:44:45 +00:00
2018-10-31 07:19:18 +00:00
// put market maker shares on the market
StockMarket_UpdateSellOrder( stockid, STOCK_MM_USER_ID, g_stockMarketData[ stockid ] [ E_IPO_SHARES ] );
2018-10-26 00:44:45 +00:00
}
2018-10-24 21:32:18 +00:00
return 1;
}
2018-11-01 22:57:08 +00:00
thread StockMarket_InsertReport( stockid, Float: default_start_pool, Float: default_start_price )
2018-10-25 17:58:12 +00:00
{
2018-11-01 22:57:08 +00:00
// set the new price of the company
new // TODO: use parabola for factor difficulty?
2018-11-03 05:06:58 +00:00
Float: new_price = ( g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ] / STOCK_MARKET_POOL_FACTOR) * STOCK_MARKET_PRICE_FACTOR + g_stockMarketData[ stockid ] [ E_IPO_PRICE ]; // STOCK_MARKET_PRICE_FLOOR;
2018-11-01 22:57:08 +00:00
if ( new_price > g_stockMarketData[ stockid ] [ E_MAX_PRICE ] ) { // dont want wild market caps
new_price = g_stockMarketData[ stockid ] [ E_MAX_PRICE ];
}
else if ( new_price < g_stockMarketData[ stockid ] [ E_IPO_PRICE ] ) { // force a minimum of IPO price
new_price = g_stockMarketData[ stockid ] [ E_IPO_PRICE ];
}
g_stockMarketReportData[ stockid ] [ 0 ] [ E_PRICE ] = new_price;
mysql_single_query( sprintf( "UPDATE `STOCK_REPORTS` SET `PRICE` = %f WHERE `ID` = %d", g_stockMarketReportData[ stockid ] [ 0 ] [ E_PRICE ], g_stockMarketReportData[ stockid ] [ 0 ] [ E_SQL_ID ] ) );
// store temporary stock info
new temp_stock_price_data[ MAX_STOCKS ] [ STOCK_REPORTING_PERIODS ] [ E_STOCK_MARKET_PRICE_DATA ];
temp_stock_price_data = g_stockMarketReportData;
// shift all report data by one
for ( new r = 0; r < sizeof( g_stockMarketReportData[ ] ) - 2; r ++ ) {
g_stockMarketReportData[ stockid ] [ r + 1 ] [ E_SQL_ID ] = temp_stock_price_data[ stockid ] [ r ] [ E_SQL_ID ];
g_stockMarketReportData[ stockid ] [ r + 1 ] [ E_POOL ] = temp_stock_price_data[ stockid ] [ r ] [ E_POOL ];
g_stockMarketReportData[ stockid ] [ r + 1 ] [ E_PRICE ] = temp_stock_price_data[ stockid ] [ r ] [ E_PRICE ];
}
// reset earnings
2018-10-30 22:42:10 +00:00
g_stockMarketReportData[ stockid ] [ 0 ] [ E_SQL_ID ] = cache_insert_id( );
2018-11-01 22:57:08 +00:00
g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ] = default_start_pool;
g_stockMarketReportData[ stockid ] [ 0 ] [ E_PRICE ] = default_start_price;
2018-10-25 17:58:12 +00:00
return 1;
}
2018-10-26 00:44:45 +00:00
thread StockMarket_OnPurchaseOrder( playerid, stockid, Float: shares )
{
new
rows = cache_get_row_count( );
if ( ! rows ) {
return SendError( playerid, "This stock has no available shares for sale." );
}
2018-10-31 07:19:18 +00:00
// check if quantity is valid
new
Float: available_quantity = 0.0;
for ( new r = 0; r < rows; r ++ ) {
available_quantity += cache_get_field_content_float( r, "SHARES" );
}
if ( shares > available_quantity ) {
return SendError( playerid, "There are not that many shares available for sale." ), StockMarket_ShowBuySlip( playerid, stockid ), 1;
}
2018-10-26 00:44:45 +00:00
// check if the player has the money for the purchase
2018-10-30 22:42:10 +00:00
new Float: ask_price = g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ];
2018-10-26 00:44:45 +00:00
new purchase_cost = floatround( ask_price * shares );
2018-10-31 08:01:35 +00:00
new Float: purchase_fee = ask_price * shares * STOCK_MARKET_TRADING_FEE;
UpdateServerVariableFloat( "stock_trading_fees", GetServerVariableFloat( "stock_trading_fees" ) + ( purchase_fee / 1000.0 ) );
new purchase_cost_plus_fee = purchase_cost + floatround( purchase_fee );
if ( GetPlayerCash( playerid ) < purchase_cost_plus_fee ) {
return SendError( playerid, "You need at least %s to purchase this many shares.", cash_format( purchase_cost_plus_fee ) ), StockMarket_ShowBuySlip( playerid, stockid ), 1;
2018-10-26 00:44:45 +00:00
}
2018-10-31 07:19:18 +00:00
new
Float: amount_remaining = shares;
2018-10-26 00:44:45 +00:00
2018-10-31 07:19:18 +00:00
for ( new row = 0; row < rows; row ++ )
{
new sell_order_user_id = cache_get_field_content_int( row, "USER_ID" );
new Float: sell_order_shares = cache_get_field_content_float( row, "SHARES" );
2018-10-26 00:44:45 +00:00
2018-10-31 07:19:18 +00:00
// check if seller is online
new
sellerid;
foreach ( sellerid : Player ) if ( GetPlayerAccountID( sellerid ) == sell_order_user_id ) {
break;
}
new Float: sold_shares = amount_remaining > sell_order_shares ? sell_order_shares : amount_remaining;
2018-10-31 08:01:35 +00:00
2018-10-31 09:05:47 +00:00
StockMarket_CreateTradeLog( stockid, GetPlayerAccountID( playerid ), sell_order_user_id, sold_shares, ask_price );
2018-10-31 08:01:35 +00:00
new Float: sold_amount_fee = sold_shares * ask_price * STOCK_MARKET_TRADING_FEE;
UpdateServerVariableFloat( "stock_trading_fees", GetServerVariableFloat( "stock_trading_fees" ) + ( sold_amount_fee / 1000.0 ) );
new sold_amount_minus_fee = floatround( sold_shares * ask_price - sold_amount_fee );
2018-10-31 07:19:18 +00:00
2018-11-04 23:33:37 +00:00
if ( 0 <= sellerid < MAX_PLAYERS && Iter_Contains( Player, sellerid ) && IsPlayerLoggedIn( sellerid ) ) {
2018-10-31 08:01:35 +00:00
GivePlayerBankMoney( sellerid, sold_amount_minus_fee ), Beep( sellerid );
SendServerMessage( sellerid, "You have sold %s %s shares to %s(%d) for "COL_GOLD"%s"COL_WHITE" (plus %0.1f%s fee)!", number_format( sold_shares, .decimals = 2 ), g_stockMarketData[ stockid ] [ E_NAME ], ReturnPlayerName( playerid ), playerid, cash_format( sold_amount_minus_fee ), STOCK_MARKET_TRADING_FEE * 100.0, "%%" );
2018-10-31 07:19:18 +00:00
} else {
2018-10-31 08:01:35 +00:00
mysql_single_query( sprintf( "UPDATE `USERS` SET `BANKMONEY` = `BANKMONEY` + %d WHERE `ID` = %d", sold_amount_minus_fee, sell_order_user_id ) );
2018-10-31 07:19:18 +00:00
}
// remove the sell order if there is little to no shares available
if ( sell_order_shares - amount_remaining < 1.0 )
{
// get rid of this sell order
mysql_single_query( sprintf( "DELETE FROM `STOCK_SELL_ORDERS` WHERE `USER_ID`=%d and `STOCK_ID`=%d", sell_order_user_id, stockid ) );
// deduct the sell order amount from amount remaining
amount_remaining -= sell_order_shares;
}
else
{
// reduce sell order quantity
StockMarket_UpdateSellOrder( stockid, sell_order_user_id, -amount_remaining );
// the player's buy order was filled in the single sell order ... prevent updating
break;
}
}
2018-10-26 00:44:45 +00:00
// increment the players shares
StockMarket_GiveShares( stockid, GetPlayerAccountID( playerid ), shares );
// reduce player balance and alert
2018-10-31 08:01:35 +00:00
GivePlayerCash( playerid, -purchase_cost_plus_fee );
SendServerMessage( playerid, "You have purchased %s shares of %s (@ %s/ea) for %s. (inc. %0.1f%s fee)", number_format( shares, .decimals = 2 ), g_stockMarketData[ stockid ] [ E_NAME ], cash_format( ask_price, .decimals = 2 ), cash_format( purchase_cost_plus_fee ), STOCK_MARKET_TRADING_FEE * 100.0, "%%" );
2018-10-26 00:44:45 +00:00
return 1;
}
thread StockMarket_OnShowBuySlip( playerid, stockid )
{
new
rows = cache_get_row_count( );
if ( ! rows ) {
2018-10-31 07:19:18 +00:00
return SendError( playerid, "This stock does not currently have any shares available to buy." );
2018-10-26 00:44:45 +00:00
}
new
2018-10-31 07:19:18 +00:00
Float: available_quantity = cache_get_field_content_float( 0, "SALE_SHARES" );
2018-10-26 00:44:45 +00:00
format(
szBigString, sizeof ( szBigString ),
""COL_WHITE"You can buy shares of %s for "COL_GREEN"%s"COL_WHITE" each.\n\nThere are %s available shares to buy.",
g_stockMarketData[ stockid ] [ E_NAME ],
2018-10-30 22:42:10 +00:00
cash_format( g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ], .decimals = 2 ),
2018-10-31 08:01:35 +00:00
number_format( available_quantity, .decimals = 2 )
2018-10-26 00:44:45 +00:00
);
ShowPlayerDialog( playerid, DIALOG_STOCK_MARKET_BUY, DIALOG_STYLE_INPUT, ""COL_WHITE"Stock Market", szBigString, "Buy", "Close" );
return 1;
}
thread StockMarket_OnShowShares( playerid )
{
new
rows = cache_get_row_count( );
if ( ! rows ) {
2018-10-31 07:19:18 +00:00
return SendError( playerid, "You are not holding any shares of any company." );
2018-10-26 00:44:45 +00:00
}
szLargeString = ""COL_WHITE"Stock\t"COL_WHITE"Total Shares\t"COL_WHITE"Current Price ($)\t"COL_GREEN"Value ($)\n";
for ( new row = 0; row < rows; row ++ )
{
new
2018-11-01 22:57:08 +00:00
stockid = cache_get_field_content_int( row, "STOCK_ID" );
2018-10-26 00:44:45 +00:00
if ( Iter_Contains( stockmarkets, stockid ) )
{
2018-10-31 07:34:50 +00:00
new Float: current_price = g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ];
2018-10-26 00:44:45 +00:00
new Float: shares = cache_get_field_content_float( row, "SHARES" );
format(
szLargeString, sizeof( szLargeString ),
2018-11-01 22:57:08 +00:00
"%s%s (%s)\t%s (%0.2f%%)\t%s\t"COL_GREEN"%s\n",
2018-10-26 00:44:45 +00:00
szLargeString,
g_stockMarketData[ stockid ] [ E_NAME ],
g_stockMarketData[ stockid ] [ E_SYMBOL ],
2018-10-31 08:01:35 +00:00
number_format( shares, .decimals = 2 ),
2018-11-01 22:57:08 +00:00
( shares / g_stockMarketData[ stockid ] [ E_MAX_SHARES ] ) * 100.0,
2018-10-26 00:44:45 +00:00
cash_format( current_price, .decimals = 2 ),
cash_format( floatround( shares * current_price ) )
);
2018-10-31 07:19:18 +00:00
// store player stocks in a variable for reference
p_PlayerShares[ playerid ] [ stockid ] = shares;
2018-10-26 00:44:45 +00:00
}
}
2018-10-31 07:19:18 +00:00
return ShowPlayerDialog( playerid, DIALOG_PLAYER_STOCKS, DIALOG_STYLE_TABLIST_HEADERS, ""COL_WHITE"Stock Market", szLargeString, "Sell", "Close" ), 1;
2018-10-26 00:44:45 +00:00
}
2018-10-30 22:42:10 +00:00
thread Stock_OnDividendPayout( stockid )
{
new
rows = cache_get_row_count( );
// pay out existing shareholders
if ( rows )
{
new
Float: total_shares = g_stockMarketData[ stockid ] [ E_MAX_SHARES ];
for ( new row = 0; row < rows; row ++ )
{
new account_id = cache_get_field_content_int( row, "USER_ID" );
new Float: shares_owned = cache_get_field_content_float( row, "SHARES" );
new Float: dividend_rate = shares_owned / total_shares;
new dividend_payout = floatround( g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ] * dividend_rate );
new
shareholder;
foreach ( shareholder : Player ) if ( GetPlayerAccountID( shareholder ) == account_id ) {
break;
}
if ( 0 <= shareholder < MAX_PLAYERS && Iter_Contains( Player, shareholder ) ) {
GivePlayerBankMoney( shareholder, dividend_payout ), Beep( shareholder );
2018-11-01 06:48:25 +00:00
SendServerMessage( shareholder, "You have been paid a "COL_GOLD"%s"COL_WHITE" dividend (%0.2f%s) for owning %s!", cash_format( dividend_payout ), dividend_rate * 100.0, "%%", g_stockMarketData[ stockid ] [ E_NAME ] );
2018-10-30 22:42:10 +00:00
} else {
mysql_single_query( sprintf( "UPDATE `USERS` SET `BANKMONEY` = `BANKMONEY` + %d WHERE `ID` = %d", dividend_payout, account_id ) );
}
}
}
2018-11-01 22:57:08 +00:00
// insert to database a new report
mysql_format( dbHandle, szBigString, sizeof ( szBigString ), "INSERT INTO `STOCK_REPORTS` (`STOCK_ID`, `POOL`, `PRICE`) VALUES (%d, %f, %f)", stockid, STOCK_DEFAULT_START_POOL, STOCK_DEFAULT_START_PRICE );
mysql_tquery( dbHandle, szBigString, "StockMarket_InsertReport", "dff", stockid, STOCK_DEFAULT_START_POOL, STOCK_DEFAULT_START_PRICE );
2018-10-30 22:42:10 +00:00
return 1;
}
thread Stock_UpdateMaximumShares( stockid )
{
new
rows = cache_get_row_count( );
2018-10-31 07:34:50 +00:00
if ( rows )
{
2018-10-31 07:19:18 +00:00
g_stockMarketData[ stockid ] [ E_MAX_SHARES ] = cache_get_field_content_float( 0, "SHARES_OWNED" ) + cache_get_field_content_float( 0, "SHARES_HELD" );
2018-10-31 07:34:50 +00:00
// rows shown but still showing as 0 maximum shares? set it to the ipo issued amount
if ( ! g_stockMarketData[ stockid ] [ E_MAX_SHARES ] )
{
g_stockMarketData[ stockid ] [ E_MAX_SHARES ] = g_stockMarketData[ stockid ] [ E_IPO_SHARES ];
}
}
else
{
2018-10-30 22:42:10 +00:00
g_stockMarketData[ stockid ] [ E_MAX_SHARES ] = g_stockMarketData[ stockid ] [ E_IPO_SHARES ];
}
return 1;
}
2018-10-31 07:19:18 +00:00
thread StockMarket_OnCancelOrder( playerid )
{
new
rows = cache_get_row_count( );
if ( rows )
{
new
player_account = GetPlayerAccountID( playerid );
for ( new row = 0; row < rows; row ++ )
{
new stockid = cache_get_field_content_int( row, "STOCK_ID" );
new Float: shares = cache_get_field_content_float( row, "SHARES" );
mysql_single_query( sprintf( "DELETE FROM `STOCK_SELL_ORDERS` WHERE `STOCK_ID`=%d AND `USER_ID`=%d", stockid, player_account ) );
StockMarket_GiveShares( stockid, player_account, shares );
2018-10-31 07:34:50 +00:00
2018-10-31 08:01:35 +00:00
SendServerMessage( playerid, "You have cancelled your order of to sell %s shares of %s.", number_format( shares, .decimals = 2 ), g_stockMarketData[ stockid ] [ E_NAME ] );
2018-10-31 07:19:18 +00:00
}
return 1;
}
else
{
return SendError( playerid, "You have no stock market sell orders to cancel." ), 1;
}
}
2018-10-24 21:32:18 +00:00
/* ** Command ** */
2018-10-26 00:44:45 +00:00
CMD:stocks( playerid, params[ ] ) return cmd_stockmarkets( playerid, params );
2018-10-24 21:32:18 +00:00
CMD:stockmarkets( playerid, params[ ] )
{
2018-10-31 07:19:18 +00:00
szLargeString = ""COL_WHITE"Stock\t"COL_WHITE"Max Shares\t"COL_WHITE"Dividend Per Share ($)\t"COL_WHITE"Price ($)\n";
2018-10-24 21:32:18 +00:00
foreach ( new s : stockmarkets )
{
2018-11-01 22:57:08 +00:00
new Float: price_change = ( ( g_stockMarketReportData[ s ] [ 1 ] [ E_PRICE ] / g_stockMarketReportData[ s ] [ 2 ] [ E_PRICE ] ) - 1.0 ) * 100.0;
new Float: payout = g_stockMarketReportData[ s ] [ 0 ] [ E_POOL ] / g_stockMarketData[ s ] [ E_MAX_SHARES ];
2018-10-24 21:32:18 +00:00
format(
szLargeString, sizeof( szLargeString ),
2018-11-01 22:57:08 +00:00
"%s%s (%s)\t%s\t"COL_GREEN"%s\t%s%s (%s%%)\n",
2018-10-24 21:32:18 +00:00
szLargeString,
g_stockMarketData[ s ] [ E_NAME ],
g_stockMarketData[ s ] [ E_SYMBOL ],
2018-10-30 22:42:10 +00:00
number_format( g_stockMarketData[ s ] [ E_MAX_SHARES ], .decimals = 0 ),
2018-10-31 07:19:18 +00:00
cash_format( payout, .decimals = 2 ),
2018-11-01 22:57:08 +00:00
price_change >= 0.0 ? COL_GREEN : COL_RED,
cash_format( g_stockMarketReportData[ s ] [ 1 ] [ E_PRICE ], .decimals = 2 ),
number_format( price_change, .decimals = 2, .prefix = ( price_change >= 0.0 ? '+' : '\0' ) )
2018-10-24 21:32:18 +00:00
);
}
2018-10-30 22:42:10 +00:00
SendServerMessage( playerid, "The stock market will payout dividends in %s.", secondstotime( GetServerVariableInt( "stock_report_time" ) - GetServerTime( ) ) );
2018-10-26 00:44:45 +00:00
return ShowPlayerDialog( playerid, DIALOG_STOCK_MARKET, DIALOG_STYLE_TABLIST_HEADERS, ""COL_WHITE"Stock Market", szLargeString, "Buy", "Close" );
}
CMD:shares( playerid, params[ ] )
{
2018-10-31 07:19:18 +00:00
if ( strmatch( params, "cancel" ) ) {
// todo: work with dialogs
mysql_tquery( dbHandle, sprintf( "SELECT * FROM `STOCK_SELL_ORDERS` WHERE `USER_ID` = %d", GetPlayerAccountID( playerid ) ), "StockMarket_OnCancelOrder", "d", playerid );
return 1;
}
2018-10-26 00:44:45 +00:00
mysql_tquery( dbHandle, sprintf( "SELECT * FROM `STOCK_OWNERS` WHERE `USER_ID` = %d", GetPlayerAccountID( playerid ) ), "StockMarket_OnShowShares", "d", playerid );
return 1;
2018-10-24 21:32:18 +00:00
}
2018-10-24 14:19:48 +00:00
/* ** Functions ** */
2018-10-31 09:05:47 +00:00
static stock CreateStockMarket( stockid, const name[ 64 ], const symbol[ 4 ], Float: ipo_shares, Float: ipo_price, Float: max_price )
2018-10-24 21:32:18 +00:00
{
if ( ! Iter_Contains( stockmarkets, stockid ) )
{
strcpy( g_stockMarketData[ stockid ] [ E_NAME ], name );
strcpy( g_stockMarketData[ stockid ] [ E_SYMBOL ], symbol );
2018-10-26 00:44:45 +00:00
g_stockMarketData[ stockid ] [ E_IPO_SHARES ] = ipo_shares;
g_stockMarketData[ stockid ] [ E_IPO_PRICE ] = ipo_price;
2018-10-31 07:19:18 +00:00
g_stockMarketData[ stockid ] [ E_MAX_PRICE ] = max_price;
2018-10-24 21:32:18 +00:00
// reset stock price information
2018-10-30 22:42:10 +00:00
for ( new r = 0; r < sizeof( g_stockMarketReportData[ ] ); r ++ ) {
2018-10-31 07:19:18 +00:00
g_stockMarketReportData[ stockid ] [ r ] [ E_POOL ] = 0.0;
2018-11-01 22:57:08 +00:00
g_stockMarketReportData[ stockid ] [ r ] [ E_PRICE ] = 0.0;
2018-10-24 21:32:18 +00:00
}
// load price information if there is
2018-11-01 22:57:08 +00:00
print( sprintf( "SELECT * FROM `STOCK_REPORTS` WHERE `STOCK_ID`=%d ORDER BY `REPORTING_TIME` DESC LIMIT %d", stockid, sizeof( g_stockMarketReportData[ ] ) ));
mysql_tquery( dbHandle, sprintf( "SELECT * FROM `STOCK_REPORTS` WHERE `STOCK_ID`=%d ORDER BY `REPORTING_TIME` DESC, `ID` DESC LIMIT %d", stockid, sizeof( g_stockMarketReportData[ ] ) ), "Stock_UpdateReportingPeriods", "d", stockid );
2018-10-30 22:42:10 +00:00
// load the maximum number of shares
2018-10-31 07:19:18 +00:00
mysql_tquery( dbHandle, sprintf( "SELECT (SELECT SUM(`SHARES`) FROM `STOCK_OWNERS` WHERE `STOCK_ID`=%d) AS `SHARES_OWNED`, (SELECT SUM(`SHARES`) FROM `STOCK_SELL_ORDERS` WHERE `STOCK_ID`=%d) AS `SHARES_HELD`", stockid, stockid ), "Stock_UpdateMaximumShares", "d", stockid );
2018-10-24 21:32:18 +00:00
// add to iterator
Iter_Add( stockmarkets, stockid );
}
return stockid;
}
2018-10-30 22:42:10 +00:00
static stock StockMarket_ReleaseDividends( stockid )
2018-10-24 21:32:18 +00:00
{
2018-10-31 07:19:18 +00:00
mysql_format( dbHandle, szBigString, sizeof ( szBigString ), "SELECT * FROM `STOCK_OWNERS` WHERE `STOCK_ID`=%d", stockid );
2018-10-30 22:42:10 +00:00
mysql_tquery( dbHandle, szBigString, "Stock_OnDividendPayout", "d", stockid );
2018-10-25 17:58:12 +00:00
return 1;
}
2018-10-24 21:32:18 +00:00
2018-11-01 10:55:21 +00:00
stock StockMarket_UpdateEarnings( stockid, amount, Float: factor = 1.0 )
2018-10-25 17:58:12 +00:00
{
if ( ! Iter_Contains( stockmarkets, stockid ) )
return 0;
2018-10-24 21:32:18 +00:00
2018-11-01 10:55:21 +00:00
// ensure that pool remains always above 0 dollars
if ( ( g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ] += float( amount ) * factor ) < 0.0 ) {
g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ] = 0.0;
}
// save to database
2018-10-31 07:34:50 +00:00
mysql_single_query( sprintf( "UPDATE `STOCK_REPORTS` SET `POOL`=%f WHERE `ID` = %d", g_stockMarketReportData[ stockid ] [ 0 ] [ E_POOL ], g_stockMarketReportData[ stockid ] [ 0 ] [ E_SQL_ID ] ) );
2018-10-25 17:58:12 +00:00
return 1;
}
2018-10-24 21:32:18 +00:00
2018-10-31 09:05:47 +00:00
static stock StockMarket_GiveShares( stockid, accountid, Float: shares )
2018-10-26 00:44:45 +00:00
{
mysql_format(
dbHandle, szBigString, sizeof ( szBigString ),
"INSERT INTO `STOCK_OWNERS` (`USER_ID`, `STOCK_ID`, `SHARES`) VALUES (%d, %d, %f) ON DUPLICATE KEY UPDATE `SHARES` = `SHARES` + %f",
accountid, stockid, shares, shares
);
mysql_single_query( szBigString );
2018-10-24 21:32:18 +00:00
}
2018-10-31 09:05:47 +00:00
static stock StockMarket_UpdateSellOrder( stockid, accountid, Float: shares )
2018-10-31 07:19:18 +00:00
{
mysql_format(
dbHandle, szBigString, sizeof ( szBigString ),
"INSERT INTO `STOCK_SELL_ORDERS` (`STOCK_ID`, `USER_ID`, `SHARES`) VALUES (%d, %d, %f) ON DUPLICATE KEY UPDATE `SHARES` = `SHARES` + %f",
stockid, accountid, shares, shares
);
mysql_single_query( szBigString );
}
2018-10-31 09:05:47 +00:00
static stock StockMarket_CreateTradeLog( stockid, buyer_acc, seller_acc, Float: shares, Float: price )
{
mysql_format(
dbHandle, szBigString, sizeof ( szBigString ),
"INSERT INTO `STOCK_TRADE_LOG` (`STOCK_ID`, `BUYER_ID`, `SELLER_ID`, `SHARES`, `PRICE`) VALUES (%d, %d, %d, %f, %f)",
stockid, buyer_acc, seller_acc, shares, price
);
mysql_single_query( szBigString );
}
2018-10-26 00:44:45 +00:00
static stock StockMarket_ShowBuySlip( playerid, stockid )
2018-10-25 17:58:12 +00:00
{
2018-10-31 07:19:18 +00:00
mysql_tquery( dbHandle, sprintf( "SELECT SUM(`SHARES`) AS `SALE_SHARES` FROM `STOCK_SELL_ORDERS` WHERE `STOCK_ID`=%d", stockid ), "StockMarket_OnShowBuySlip", "dd", playerid, stockid );
return 1;
}
static stock StockMarket_ShowSellSlip( playerid, stockid )
{
format(
szLargeString, sizeof ( szLargeString ),
""COL_WHITE"You can sell shares of %s for "COL_GREEN"%s"COL_WHITE" each.\n\nThough, you will have to wait until a person buys them.\n\nYou have %s available shares to sell.",
g_stockMarketData[ stockid ] [ E_NAME ],
cash_format( g_stockMarketReportData[ stockid ] [ 1 ] [ E_PRICE ], .decimals = 2 ),
2018-10-31 08:01:35 +00:00
number_format( p_PlayerShares[ playerid ] [ stockid ], .decimals = 2 )
2018-10-31 07:19:18 +00:00
);
ShowPlayerDialog( playerid, DIALOG_STOCK_MARKET_SELL, DIALOG_STYLE_INPUT, ""COL_WHITE"Stock Market", szLargeString, "Sell", "Close" );
2018-10-25 17:58:12 +00:00
return 1;
2018-10-24 21:32:18 +00:00
}
/*
2018-10-31 07:19:18 +00:00
DROP TABLE `STOCK_REPORTS`;
2018-10-24 21:32:18 +00:00
CREATE TABLE IF NOT EXISTS `STOCK_REPORTS` (
2018-10-25 17:58:12 +00:00
`ID` int(11) primary key auto_increment,
2018-10-24 21:32:18 +00:00
`STOCK_ID` int(11),
2018-10-30 22:42:10 +00:00
`POOL` float,
2018-10-31 07:19:18 +00:00
`PRICE` float,
2018-10-24 21:32:18 +00:00
`REPORTING_TIME` TIMESTAMP default CURRENT_TIMESTAMP
);
2018-10-31 07:19:18 +00:00
DROP TABLE `STOCK_OWNERS`;
2018-10-25 17:58:12 +00:00
CREATE TABLE IF NOT EXISTS `STOCK_OWNERS` (
`USER_ID` int(11),
`STOCK_ID` int(11),
`SHARES` float,
2018-10-26 00:44:45 +00:00
PRIMARY KEY (USER_ID, STOCK_ID)
2018-10-25 17:58:12 +00:00
);
2018-10-31 07:19:18 +00:00
DROP TABLE `STOCK_SELL_ORDERS`;
CREATE TABLE IF NOT EXISTS `STOCK_SELL_ORDERS` (
2018-10-25 17:58:12 +00:00
`STOCK_ID` int(11),
2018-10-31 07:19:18 +00:00
`USER_ID` int(11),
2018-10-25 17:58:12 +00:00
`SHARES` float,
2018-10-31 07:19:18 +00:00
`LIST_DATE` TIMESTAMP default CURRENT_TIMESTAMP,
PRIMARY KEY (STOCK_ID, USER_ID)
);
2018-10-31 09:05:47 +00:00
DROP TABLE `STOCK_TRADE_LOG`;
CREATE TABLE IF NOT EXISTS `STOCK_TRADE_LOG` (
`ID` int(11) primary key auto_increment,
`STOCK_ID` int(11),
`BUYER_ID` int(11),
`SELLER_ID` int(11),
`SHARES` float,
`PRICE` float,
2018-11-01 06:48:25 +00:00
`DATE` TIMESTAMP default CURRENT_TIMESTAMP
2018-10-31 09:05:47 +00:00
);
2018-10-24 21:32:18 +00:00
*/