From 737d7c756de8a37713c03f85351af476b17f5079 Mon Sep 17 00:00:00 2001 From: sindoring Date: Sun, 19 Jul 2026 19:13:50 +0400 Subject: [PATCH] =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D0=BC=D0=B0=D0=B3=D0=B0=D0=B7=D0=B8=D0=BD=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lua_scripts/Store_System/Store_Client.lua | 4 +- lua_scripts/Store_System/Store_Server.lua | 111 +++++++++++----------- sql/Store.sql | 51 +++++++++- 3 files changed, 109 insertions(+), 57 deletions(-) diff --git a/lua_scripts/Store_System/Store_Client.lua b/lua_scripts/Store_System/Store_Client.lua index ba28da6..ec30252 100644 --- a/lua_scripts/Store_System/Store_Client.lua +++ b/lua_scripts/Store_System/Store_Client.lua @@ -144,7 +144,7 @@ function SHOP_UI.MainFrame_Create() shopFrame.Title:SetFont("Fonts\\FRIZQT__.TTF", 14) shopFrame.Title:SetShadowOffset(1, -1) shopFrame.Title:SetPoint("TOP", shopFrame, "TOP", 0, -3) - shopFrame.Title:SetText("|cffedd100Shop|r") + shopFrame.Title:SetText("|cffedd100Лавка Лунной Жрицы|r") -- create navigation button placeholders, pass parent as arg SHOP_UI.NavButtons_Create(shopFrame) @@ -1193,7 +1193,7 @@ local function ModifyGameMenuFrame() storeButton.Text:SetFont("Fonts\\FRIZQT__.TTF", 13, "OUTLINE") storeButton.Text:SetShadowOffset(1, -1) storeButton.Text:SetPoint("CENTER", storeButton, "CENTER", 0, 1) - storeButton.Text:SetText("|cffdbe005Store"); + storeButton.Text:SetText("|cffdbe005Лавка Лунной Жрицы"); -- on click open the shop frame and hide the escape menu storeButton:SetScript("OnClick", function() diff --git a/lua_scripts/Store_System/Store_Server.lua b/lua_scripts/Store_System/Store_Server.lua index 2cbfad1..ca9fc7f 100644 --- a/lua_scripts/Store_System/Store_Server.lua +++ b/lua_scripts/Store_System/Store_Server.lua @@ -7,7 +7,7 @@ local CONFIG = { mailSenderGUID = 1, -- GUID of the character shown as sender of purchase mails strings = { -- Currency name is appended to the end of this string - insufficientFunds = "You don't have enough", + insufficientFunds = "Недостаточно средств на балансе", -- Type is appended, ie. title, mount, pet etc. alreadyKnown = "You already have this", tooHighLevel = "Your level is too high", @@ -22,11 +22,24 @@ local CONFIG = { local AIO = AIO or require("AIO") and require("Store_DataStruct") local CURRENCY_TYPES = { - [1] = "GOLD", - [2] = "ITEM_TOKEN", [3] = "SERVER_HANDLED" } +local purchaseReferenceSequence = 0 + +local function GetAccountBalance(player) + local query = AuthDBQuery( + "SELECT CAST(`balance` AS DOUBLE) FROM `account_balances` WHERE `account_id` = " .. + player:GetAccountId() .. " LIMIT 1;" + ) + + if not query then + return 0 + end + + return query:GetDouble(0) +end + local SHOP_UI = { serviceHandlers = { [1] = "ItemHandler", -- Okay @@ -55,17 +68,8 @@ function StoreHandler.UpdateCurrencies(player) local val = 0 local currencyTypeText = CURRENCY_TYPES[currency[KEYS.currency.currencyType]] - -- Handle the different currency types - if(currencyTypeText == "GOLD") then - val = math.floor(player:GetCoinage() / 10000) - end - - if(currencyTypeText == "ITEM_TOKEN") then - val = player:GetItemCount(currency[KEYS.currency.data]) - end - if(currencyTypeText == "SERVER_HANDLED") then - -- Add your custom handling here for retreiving your server handled currencies + val = GetAccountBalance(player) end -- If value is larger than 10k then truncate to make sure it fits within the shop frame @@ -73,7 +77,9 @@ function StoreHandler.UpdateCurrencies(player) val = "9999+" end - table.insert(tmp, val) + -- Preserve the database currency id. A positional array silently breaks as + -- soon as ids have gaps or are reordered by MySQL. + tmp[currencyId] = val end AIO.Handle(player, "STORE_CLIENT", "UpdateCurrencies", tmp) end @@ -106,39 +112,38 @@ function StoreHandler.Purchase(player, serviceId) end -- Helper functions -function SHOP_UI.DeductCurrency(player, currencyId, amount) +function SHOP_UI.DeductCurrency(player, currencyId, amount, serviceId) local currency = GetCurrencyData() - local currencyType = currency[currencyId][KEYS.currency.currencyType] - local currencyName = currency[currencyId][KEYS.currency.name] - local currencyData = currency[currencyId][KEYS.currency.data] - - -- Gold handling - if(CURRENCY_TYPES[currencyType] == "GOLD") then - if(player:GetCoinage() < amount * 10000) then - player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.insufficientFunds.." "..currencyName.."|r") - player:PlayDirectSound(GetSoundEffect("notEnoughMoney", player:GetRace(), player:GetGender()), player) - return false - end - - player:SetCoinage(player:GetCoinage() - (amount * 10000)) - end - - -- Token handling - if(CURRENCY_TYPES[currencyType] == "ITEM_TOKEN") then - if not(player:HasItem(currencyData, amount)) then - player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.insufficientFunds.." "..currencyName.."|r") - player:PlayDirectSound(GetSoundEffect("notEnoughMoney", player:GetRace(), player:GetGender()), player) - return false - end - - player:RemoveItem(currencyData, amount) - end - - -- Other special handlingm you have to add your own integration here. - if(CURRENCY_TYPES[currencyType] == "SERVER_HANDLED") then + if not currency[currencyId] then return false end - + + local currencyType = currency[currencyId][KEYS.currency.currencyType] + if CURRENCY_TYPES[currencyType] ~= "SERVER_HANDLED" then + return false + end + + purchaseReferenceSequence = purchaseReferenceSequence + 1 + local reference = string.format( + "game-store:%u:%u:%u:%u:%u", + player:GetAccountId(), + player:GetGUIDLow(), + serviceId or 0, + os.time(), + purchaseReferenceSequence + ) + local result = AuthDBQuery( + "SELECT `store_deduct_balance`(" .. player:GetAccountId() .. ", " .. amount .. ", '" .. reference .. "', " .. + player:GetGUIDLow() .. ", " .. (serviceId or 0) .. ") AS `balance_after`;" + ) + local balanceAfter = result and result:GetDouble(0) or -1 + + if balanceAfter < 0 then + player:SendAreaTriggerMessage("|cFFFF0000" .. CONFIG.strings.insufficientFunds .. "|r") + player:PlayDirectSound(GetSoundEffect("notEnoughMoney", player:GetRace(), player:GetGender()), player) + return false + end + return true end @@ -152,7 +157,7 @@ function SHOP_UI.ItemHandler(player, data) local currency, amount = data[KEYS.service.currency], data[KEYS.service.price] - data[KEYS.service.discount] -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -178,7 +183,7 @@ function SHOP_UI.GoldHandler(player, data) local currency, amount = data[KEYS.service.currency], data[KEYS.service.price] - data[KEYS.service.discount] -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -212,7 +217,7 @@ function SHOP_UI.MountHandler(player, data) end -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -250,7 +255,7 @@ function SHOP_UI.PetHandler(player, data) end -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -271,7 +276,7 @@ function SHOP_UI.BuffHandler(player, data) local currency, amount = data[KEYS.service.currency], data[KEYS.service.price] - data[KEYS.service.discount] -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -292,7 +297,7 @@ function SHOP_UI.ServiceHandler(player, data) local currency, amount = data[KEYS.service.currency], data[KEYS.service.price] - data[KEYS.service.discount] -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -319,7 +324,7 @@ function SHOP_UI.LevelHandler(player, data) end -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -354,7 +359,7 @@ function SHOP_UI.TitleHandler(player, data) end -- Deduct currency - local deducted = SHOP_UI.DeductCurrency(player, currency, amount) + local deducted = SHOP_UI.DeductCurrency(player, currency, amount, data.ID) -- If currency was not deducted from the player, abort and send message if not(deducted) then @@ -372,4 +377,4 @@ function SHOP_UI.UnusedHandler(player, data) -- Since this is unused, always return false until the function is in use. return false -end \ No newline at end of file +end diff --git a/sql/Store.sql b/sql/Store.sql index c0f039e..6bfb9de 100644 --- a/sql/Store.sql +++ b/sql/Store.sql @@ -73,8 +73,15 @@ CREATE TABLE IF NOT EXISTS `store_currencies` ( -- Dumping data for table store.store_currencies: ~2 rows (approximately) INSERT INTO `store_currencies` (`id`, `type`, `name`, `icon`, `data`, `tooltip`) VALUES - (1, 1, 'Gold', 'Gold', 0, 'This is normal gold.'), - (2, 2, 'Item Token', 'Token', 4540, 'This is an item currency.'); + (1, 3, 'Баланс', 'Gold', 0, 'Баланс учётной записи на сайте.'); + +-- The website account balance is the only in-game store currency. These +-- statements also migrate databases bootstrapped by an older Store.sql. +UPDATE `store_currencies` +SET `type` = 3, `name` = 'Баланс', `icon` = 'Gold', `data` = 0, + `tooltip` = 'Баланс учётной записи на сайте.' +WHERE `id` = 1; +DELETE FROM `store_currencies` WHERE `id` <> 1; -- Dumping structure for table store.store_logs CREATE TABLE IF NOT EXISTS `store_logs` ( @@ -173,6 +180,46 @@ INSERT INTO `store_services` (`id`, `type`, `name`, `tooltipName`, `tooltipType` (17, 8, 'Level 60 Boost', 'Level Boost', '', 'Boosts your characters level to level 60!', 'achievement_level_60', 40, 1, 0, 0, 0, 1, 60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1), (18, 1, 'Tuxedo Set', 'Tuxedo Set', '', 'Express your great fashion sense with this full Tuxedo set!\r\n\r\nContains the following:\r\n\r\n1x Tuxedo Jacket\r\n1x Tuxedo Shirt\r\n1x Tuxedo Pants', 'inv_shirt_black_01', 50, 1, 0, 0, 0, 1, 10036, 10035, 10034, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1); +UPDATE `store_services` SET `currency` = 1; + +-- Atomic bridge between the website balance and Eluna. It both reserves the +-- funds and writes the website-compatible ledger entry before a reward is sent. +DROP FUNCTION IF EXISTS `acore_auth`.`store_deduct_balance`; +DELIMITER // +CREATE FUNCTION `acore_auth`.`store_deduct_balance`( + p_account_id INT UNSIGNED, + p_amount DECIMAL(14,2), + p_reference VARCHAR(191), + p_character_guid INT UNSIGNED, + p_service_id INT UNSIGNED +) RETURNS DECIMAL(14,2) +DETERMINISTIC +MODIFIES SQL DATA +BEGIN + DECLARE v_balance_after DECIMAL(14,2) DEFAULT -1; + + UPDATE `acore_auth`.`account_balances` + SET `balance` = `balance` - p_amount, `updated_at` = CURRENT_TIMESTAMP + WHERE `account_id` = p_account_id + AND p_amount >= 0 + AND `balance` >= p_amount; + + IF ROW_COUNT() = 1 THEN + SELECT `balance` INTO v_balance_after + FROM `acore_auth`.`account_balances` + WHERE `account_id` = p_account_id; + + INSERT INTO `acore_auth`.`balance_transactions` + (`account_id`, `type`, `amount`, `balance_after`, `reference`, `metadata`, `created_at`) + VALUES + (p_account_id, 'store_purchase', -p_amount, v_balance_after, p_reference, + JSON_OBJECT('character_guid', p_character_guid, 'service_id', p_service_id), CURRENT_TIMESTAMP); + END IF; + + RETURN v_balance_after; +END// +DELIMITER ; + /*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; /*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */;