Custom store double charging

Custom store double charging

« posted: Feb 25, 2019, 10:13 AM »
I've recently resurrected my server after being dormant for nearly a year. Which is leading to some interesting bugs on in systems that used to work perfectly.

I have a custom store for base building objects, which has learned a new very frustrating trick. It's somehow managing to run both the version of the buy script that is present in my PBO and another version of the same script (that exists somewhere somehow) at the same time.

Code for the buy button:
Code: [Select]
class BuyItem: w_RscButton
{
idc = BaseStore_buy;
action = "[0] execVM 'client\systems\BasePartsStore\buyParts.sqf'";
text = "Buy";
sizeEx = BaseStoreButton_textSize;

x = BaseStoreMainBG_X + (0.334 * SZ_SCALE);
y = BaseStoreMainBG_Y + (0.507 * SZ_SCALE);
w = 0.096 * SZ_SCALE;
h = 0.040 * SZ_SCALE;
};

Code for the buy script (complete with debug edits)

Code: [Select]
// ******************************************************************************************
// * This project is licensed under the GNU Affero GPL v3. Copyright © 2014 A3Wasteland.com *
// ******************************************************************************************
// @file Version: 1.0
// @file Name: buyParts.sqf
// @file Author: [404] Deadbeat, [404] Costlyy, GMG_Monkey
// @file Created: 20/11/2012 05:13
// @file Args: [int (0 = buy to player 1 = buy to crate)]

if (!isNil "storePurchaseHandle" && {typeName storePurchaseHandle == "SCRIPT"} && {!scriptDone storePurchaseHandle}) exitWith {hint "Please wait, your previous purchase is being processed"};

#include "dialog\BaseStoreDefines.sqf";

#define PURCHASED_CRATE_TYPE_AMMO 60
#define PURCHASED_CRATE_TYPE_WEAPON 61
 
#define CEIL_PRICE(PRICE) (ceil ((PRICE) / 5) * 5)

player globalChat "Spawning buy Parts Scipt";
storePurchaseHandle = _this spawn
{
disableSerialization;

private ["_playerMoney", "_size", "_price", "_dialog", "_itemlist", "_totalText", "_playerMoneyText", "_itemText", "_class", "_uniformClass", "_vestClass", "_backpackClass", "_itemClass", "_markerPos", "_obj", "_currentBinoc", "_confirmResult", "_successHint", "_hasNVG", "_requestKey"];
player globalChat "Starting Buy Script";
//Initialize Values
_playerMoney = player getVariable ["cmoney", 0];
_successHint = true;

// Grab access to the controls
_dialog = findDisplay BaseStore_DIALOG;
_itemlist = _dialog displayCtrl BaseStore_item_list;
_totalText = _dialog displayCtrl BaseStore_total;
_playerMoneyText = _Dialog displayCtrl BaseStore_money;

_itemIndex = lbCurSel BaseStore_item_list;
_itemText = _itemlist lbText _itemIndex;
_itemData = _itemlist lbData _itemIndex;

_showInsufficientFundsError =
{
_itemText = _this select 0;
hint parseText format ["Not enough money for<br/>""%1""", _itemText];
playSound "FD_CP_Not_Clear_F";
_price = -1;
};

_showInsufficientSpaceError =
{
_itemText = _this select 0;
hint parseText format ["Not enough space for<br/>""%1""", _itemText];
playSound "FD_CP_Not_Clear_F";
_price = -1;
};

_showItemSpawnTimeoutError =
{
_itemText = _this select 0;
hint parseText format ["<t color='#ffff00'>An unknown error occurred.</t><br/>The purchase of ""%1"" has been cancelled.", _itemText];
playSound "FD_CP_Not_Clear_F";
_price = -1;
};

_showItemSpawnedOutsideMessage =
{
_itemText = _this select 0;
hint format ["""%1"" has been spawned outside, in front of the store.", _itemText];
playSound "FD_Finish_F";
_successHint = false;
};

_showReplaceConfirmMessage =
{
_itemText = _this select 0;

if (param [1, false, [false]]) then
{
_itemText = format ["Purchasing these %1 will replace your current ones.", _itemText];
}
else
{
if (param [2, false, [false]]) then
{
_itemText = format ["Purchasing this %1 will replace your current one.", _itemText];
}
else
{
_itemText = format ["Purchasing this %1 will replace your current one, and its contents will be lost.", _itemText];
};
};

_confirmResult = [parseText _itemText, "Confirm", "Buy", true] call BIS_fnc_guiMessage;

if (!_confirmResult) then
{
_price = -1;
};

_confirmResult
};

_showAlreadyHaveItemMessage =
{
_itemText = _this select 0;

if (param [1, false, [false]]) then
{
_itemText = format ["You already have these %1.", _itemText];
}
else
{
_itemText = format ["You already have this %1.", _itemText];
};

playSound "FD_CP_Not_Clear_F";
_price = -1;

[parseText _itemText, "Error"] call BIS_fnc_guiMessage
};

if (isNil "_price") then
{
{
if (_itemData == _x select 1) exitWith
{
_class = _x select 1;
player globalChat "Setting Price";
_price = (_x select 2) / 5;
player globalchat format ["Price is: %1", _price];

// Ensure the player has enough money
player globalChat "Checking for sufficiant funds";
if (_price > _playerMoney) exitWith
{
[_itemText] call _showInsufficientFundsError;
};

_requestKey = call A3W_fnc_generateKey;
player globalChat "Requesting Item Spawn";
_x call requestStoreObject;
};
} forEach (call AllBaseParts);
};

if (!isNil "_price" && {_price > -1}) then
{
_playerMoney = player getVariable ["cmoney", 0];

// Re-check for money after purchase
player globalChat "Checking for sufficiant funds again";
player globalchat format ["Price is: %1", _price];
if ((_price) > _playerMoney) then
{
if (!isNil "_requestKey" && {!isNil _requestKey}) then
{
player globalChat "deleting item";
deleteVehicle objectFromNetId (missionNamespace getVariable _requestKey);
};

[_itemText] call _showInsufficientFundsError;
}
else
{
player globalChat "Subtracting Money";
player setVariable ["cmoney", _playerMoney - _price, true];
_playerMoneyText ctrlSetText format ["Cash: $%1", [player getVariable ["cmoney", 0]] call fn_numbersText];
if (_successHint) then { hint "Purchase successful!" };
playSound "FD_Finish_F";
};
};

if (!isNil "_requestKey" && {!isNil _requestKey}) then
{
missionNamespace setVariable [_requestKey, nil];
};

sleep 0.25; // double-click protection
};

if (typeName storePurchaseHandle == "SCRIPT") then
{
private "_storePurchaseHandle";
_storePurchaseHandle = storePurchaseHandle;
waitUntil {scriptDone _storePurchaseHandle};
};

storePurchaseHandle = nil;


Here's the fun part, one of the instances of the script will run the above, complete with price adjustment, and globalchats. The second will not. It charges full price and doesn't post any of the debug message I inserted.

The best part is it dosn't complete two purchases, it complete one. if the player has enough money for both, the base object will spawn as expected, and they'll be charged both transactions. If they have enough for one transaction it will spawn, and then be deleted for insufficient funds.

Occasionally the script that doesn't contain any edits will take priority, in that case no globalchats will post, but the outcome is the same.

I've been banging my head against this for a few days now, and it makes zero sense to me.
  • Offline AgentRev
  • Developer
  • Veteran
  • ******
  • Posts: 2544

Re: Custom store double charging

« Reply #1 posted: Feb 25, 2019, 07:52 PM »
https://github.com/A3Wasteland/ArmA3_Wasteland.Altis/commit/199f532728f4b9ff50071c4d32c420d257a3c48a

Your script is calling requestStoreObject, which itself calls spawnStoreObject. In the commit above, the cmoney transaction was moved from the client's buy scripts to the server's spawnStoreObject. Your script's cmoney is conflicting with A3W_fnc_setCMoney.

Re: Custom store double charging

« Reply #2 posted: Mar 04, 2019, 09:22 PM »
Thanks AgentRev, it was driving me nuts trying to figure out what was causing it. Catching up to a years worth of changes has been challenging.