Compare commits
3 Commits
58dcb622f3
...
solo-lfg
| Author | SHA1 | Date | |
|---|---|---|---|
| a71eb7bee7 | |||
| 4eb59b7245 | |||
| 737d7c756d |
@@ -0,0 +1,39 @@
|
||||
# Solo Random Dungeon client patch
|
||||
|
||||
The server recognizes five custom `LFGDungeons.dbc` entries:
|
||||
|
||||
| Custom ID | Stock category |
|
||||
| --- | --- |
|
||||
| 1258 | Random Classic Dungeon (258) |
|
||||
| 1259 | Random Burning Crusade Dungeon (259) |
|
||||
| 1260 | Random Burning Crusade Heroic (260) |
|
||||
| 1261 | Random Lich King Dungeon (261) |
|
||||
| 1262 | Random Lich King Heroic (262) |
|
||||
|
||||
The generated server DBC is at:
|
||||
|
||||
`client_patch/enUS/DBFilesClient/LFGDungeons.dbc`
|
||||
|
||||
It is mounted into `ac-worldserver` by `docker-compose.override.yml`.
|
||||
|
||||
## Client installation
|
||||
|
||||
Use the DBC extracted from the exact locale of the target 3.3.5a client:
|
||||
|
||||
```bash
|
||||
./tools/build-solo-lfg-dbc.py \
|
||||
/path/to/client/DBFilesClient/LFGDungeons.dbc \
|
||||
/tmp/solo-rdf/DBFilesClient/LFGDungeons.dbc
|
||||
```
|
||||
|
||||
Put the generated `DBFilesClient/LFGDungeons.dbc` into a client MPQ patch
|
||||
loaded after the stock locale archives. For a Russian client this is normally
|
||||
a new archive under `Data/ruRU/` named like `patch-ruRU-S.MPQ`.
|
||||
|
||||
After installing the patch, delete the client's `Cache` directory. The
|
||||
Dungeon Finder list will contain separate solo random categories appropriate
|
||||
for the character's level.
|
||||
|
||||
Do not replace a localized client DBC with the bundled enUS server DBC. Run
|
||||
the generator against that client's own DBC so all stock localized rows remain
|
||||
intact.
|
||||
Binary file not shown.
@@ -8,3 +8,4 @@ services:
|
||||
volumes:
|
||||
- ./modules:/azerothcore/modules:ro
|
||||
- ./lua_scripts:/azerothcore/lua_scripts
|
||||
- ./client_patch/enUS/DBFilesClient/LFGDungeons.dbc:/azerothcore/env/dist/data/dbc/LFGDungeons.dbc:ro
|
||||
|
||||
+1715993
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
||||
local CONFIG = {
|
||||
maxCategories = 11,
|
||||
strings = {
|
||||
categoryAccessDenied = "You do not have access to this category!",
|
||||
categoryAccessDenied = "У вас нет доступа к этой категории!",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
@@ -333,9 +333,9 @@ function SHOP_UI.OnPurchaseConfirm(data)
|
||||
end
|
||||
|
||||
StaticPopupDialogs["CONFIRM_STORE_PURCHASE"] = {
|
||||
text = "Are you sure you want to purchase %s?",
|
||||
button1 = "Yes",
|
||||
button2 = "No",
|
||||
text = "Вы уверены, что хотите купить %s?",
|
||||
button1 = "Да",
|
||||
button2 = "Нет",
|
||||
OnAccept = function(self, data)
|
||||
SHOP_UI.OnPurchaseConfirm(data)
|
||||
end,
|
||||
@@ -454,7 +454,7 @@ function SHOP_UI.ServiceBoxes_Create(parent)
|
||||
service.buyButton.ButtonText = service.buyButton:CreateFontString()
|
||||
service.buyButton.ButtonText:SetFont("Fonts\\FRIZQT__.TTF", 11, "OUTLINE")
|
||||
service.buyButton.ButtonText:SetPoint("CENTER", service.buyButton, 0, 0)
|
||||
service.buyButton.ButtonText:SetText("Buy now!")
|
||||
service.buyButton.ButtonText:SetText("Купить")
|
||||
|
||||
service.buyButton:SetScript(
|
||||
"OnClick",
|
||||
@@ -635,7 +635,7 @@ function SHOP_UI.ServiceBoxes_Update()
|
||||
|
||||
-- calculate discount percentage
|
||||
local discountPct = math.floor(((service.Price - service.Discount) - service.Price) / service.Price * 100)
|
||||
service.BannerText:SetFormattedText("|cffffffffOn sale: %i%%|r", discountPct)
|
||||
service.BannerText:SetFormattedText("|cffffffffСкидка: %i%%|r", discountPct)
|
||||
|
||||
-- if service is discounted, then show all the discount frames and override the price text. otherwise hide.
|
||||
if service.Discount > 1 then
|
||||
@@ -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()
|
||||
|
||||
@@ -7,13 +7,15 @@ 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",
|
||||
mailBody = "Thank you for your purchase!",
|
||||
alreadyKnownMount = "Этот транспорт уже изучен",
|
||||
alreadyKnownPet = "Этот питомец уже изучен",
|
||||
alreadyKnownTitle = "Этот титул уже получен",
|
||||
tooHighLevel = "Уровень персонажа слишком высок",
|
||||
mailBody = "Благодарим за покупку в Лавке Лунной Жрицы!",
|
||||
-- The service name is prefixed to this message
|
||||
successfulPurchase = "successfully purchased!"
|
||||
successfulPurchase = "Покупка успешно совершена!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,11 +24,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 +70,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 +79,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
|
||||
@@ -99,46 +107,45 @@ function StoreHandler.Purchase(player, serviceId)
|
||||
player:PlayDirectSound(120, player)
|
||||
|
||||
-- Send success toast
|
||||
player:SendAreaTriggerMessage(services[serviceId][KEYS.service.name] .. " "..CONFIG.strings.successfulPurchase)
|
||||
player:SendAreaTriggerMessage(CONFIG.strings.successfulPurchase)
|
||||
end
|
||||
end
|
||||
end
|
||||
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 +159,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
|
||||
@@ -169,7 +176,7 @@ function SHOP_UI.ItemHandler(player, data)
|
||||
end
|
||||
|
||||
-- Send reward mail
|
||||
SendMail("Purchase of: "..data[KEYS.service.name], CONFIG.strings.mailBody, player:GetGUIDLow(), CONFIG.mailSenderGUID, 62, 0, 0, 0, unpack(items))
|
||||
SendMail("Покупка: "..data[KEYS.service.name], CONFIG.strings.mailBody, player:GetGUIDLow(), CONFIG.mailSenderGUID, 62, 0, 0, 0, unpack(items))
|
||||
return true
|
||||
end
|
||||
|
||||
@@ -178,7 +185,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
|
||||
@@ -206,13 +213,13 @@ function SHOP_UI.MountHandler(player, data)
|
||||
|
||||
-- check if player already has the spells learned
|
||||
if(knownCount == rewardCount) then
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnown.." mount|r")
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnownMount.."|r")
|
||||
player:PlayDirectSound(GetSoundEffect("cantLearn", player:GetRace(), player:GetGender()), player)
|
||||
return false
|
||||
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
|
||||
@@ -244,13 +251,13 @@ function SHOP_UI.PetHandler(player, data)
|
||||
|
||||
-- check if player already has the spells learned
|
||||
if(knownCount == rewardCount) then
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnown.." pet|r")
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnownPet.."|r")
|
||||
player:PlayDirectSound(GetSoundEffect("cantLearn", player:GetRace(), player:GetGender()), player)
|
||||
return false
|
||||
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 +278,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 +299,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 +326,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
|
||||
@@ -348,13 +355,13 @@ function SHOP_UI.TitleHandler(player, data)
|
||||
|
||||
-- Check whether or not the player already has the specified title
|
||||
if(player:HasTitle(data[KEYS.service.reward_1])) then
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnown.." title|r")
|
||||
player:SendAreaTriggerMessage("|cFFFF0000"..CONFIG.strings.alreadyKnownTitle.."|r")
|
||||
player:PlayDirectSound(GetSoundEffect("cantLearn", player:GetRace(), player:GetGender()), player)
|
||||
return false
|
||||
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 +379,4 @@ function SHOP_UI.UnusedHandler(player, data)
|
||||
|
||||
-- Since this is unused, always return false until the function is in use.
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
+111
-23
@@ -27,16 +27,23 @@ CREATE TABLE IF NOT EXISTS `store_categories` (
|
||||
|
||||
-- Dumping data for table store.store_categories: ~9 rows (approximately)
|
||||
INSERT INTO `store_categories` (`id`, `name`, `icon`, `requiredRank`, `flags`, `enabled`) VALUES
|
||||
(1, 'Featured', 'inv_helmet_96', 0, 2, 1),
|
||||
(2, 'Titles', 'inv_scroll_11', 0, 0, 1),
|
||||
(3, 'Items', 'ability_warrior_challange', 0, 0, 1),
|
||||
(4, 'Mounts & Pets', 'inv_box_petcarrier_01', 0, 0, 1),
|
||||
(5, 'Boosts', 'spell_holy_surgeoflight', 0, 0, 1),
|
||||
(6, 'Services', 'vas_charactertransfer', 0, 0, 1),
|
||||
(7, 'Buffs', 'spell_holy_holynova', 0, 0, 1),
|
||||
(8, 'Outlet', 'inv_misc_toy_07', 0, 1, 1),
|
||||
(1, 'Рекомендуем', 'inv_helmet_96', 0, 2, 1),
|
||||
(2, 'Титулы', 'inv_scroll_11', 0, 0, 1),
|
||||
(3, 'Предметы', 'ability_warrior_challange', 0, 0, 1),
|
||||
(4, 'Транспорт и питомцы', 'inv_box_petcarrier_01', 0, 0, 1),
|
||||
(5, 'Усиления', 'spell_holy_surgeoflight', 0, 0, 1),
|
||||
(6, 'Услуги', 'vas_charactertransfer', 0, 0, 1),
|
||||
(7, 'Эффекты', 'spell_holy_holynova', 0, 0, 1),
|
||||
(8, 'Распродажа', 'inv_misc_toy_07', 0, 1, 1),
|
||||
(9, 'VIP', 'inv_misc_note_03', 4, 0, 1);
|
||||
|
||||
UPDATE `store_categories` SET `name` = CASE `id`
|
||||
WHEN 1 THEN 'Рекомендуем' WHEN 2 THEN 'Титулы' WHEN 3 THEN 'Предметы'
|
||||
WHEN 4 THEN 'Транспорт и питомцы' WHEN 5 THEN 'Усиления' WHEN 6 THEN 'Услуги'
|
||||
WHEN 7 THEN 'Эффекты' WHEN 8 THEN 'Распродажа' WHEN 9 THEN 'VIP'
|
||||
ELSE `name` END
|
||||
WHERE `id` BETWEEN 1 AND 9;
|
||||
|
||||
-- Dumping structure for table store.store_category_service_link
|
||||
CREATE TABLE IF NOT EXISTS `store_category_service_link` (
|
||||
`category` int unsigned NOT NULL,
|
||||
@@ -73,8 +80,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` (
|
||||
@@ -159,19 +173,93 @@ CREATE TABLE IF NOT EXISTS `store_services` (
|
||||
|
||||
-- Dumping data for table store.store_services: ~13 rows (approximately)
|
||||
INSERT INTO `store_services` (`id`, `type`, `name`, `tooltipName`, `tooltipType`, `tooltipText`, `icon`, `price`, `currency`, `hyperlinkId`, `creatureEntry`, `discountAmount`, `flags`, `reward_1`, `reward_2`, `reward_3`, `reward_4`, `reward_5`, `reward_6`, `reward_7`, `reward_8`, `rewardcount_1`, `rewardcount_2`, `rewardcount_3`, `rewardcount_4`, `rewardcount_5`, `rewardcount_6`, `rewardcount_7`, `rewardcount_8`, `new`, `enabled`) VALUES
|
||||
(1, 8, 'Level Boost\r\n+10 Levels', 'Level Boost', '', 'Increase your characters levels by 10.', 'achievement_level_10', 10, 2, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(2, 7, 'Faction Change', 'Faction Change', '', 'Allows you to change your characters faction. Available after relogging.', 'vas_factionchange', 5, 1, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(3, 7, 'Race Change', 'Race Change', '', 'Allows you to change your characters race. Available after relogging.', 'vas_racechange', 10, 1, 0, 0, 5, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(4, 7, 'Name Change', 'Name Change', '', 'Allows you to change your characters name. Available after relogging.', 'vas_namechange', 5, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(5, 1, 'Foam Sword\r\n(Two-Hand Sword)', '', 'item', '|cff00FFFFClick to preview!|r', 'inv_sword_22', 10, 1, 45061, 45061, 0, 1, 45061, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(8, 3, 'Swift Spectral Tiger\r\n(Mount)', '', 'spell', '|cff00FFFFClick to preview!|r', 'ability_mount_spectraltiger', 30, 1, 42777, 24004, 0, 0, 42777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1),
|
||||
(12, 1, 'Epic Purple Shirt\r\n(Shirt)', '', 'item', '|cff00FFFFClick to preview!|r', 'inv_shirt_purple_01', 10, 1, 45037, 45037, 5, 1, 45037, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(13, 4, 'Prairie Chicken\r\n(Pet)', '', 'spell', '|cff00FFFFClick to preview!|r', 'spell_magic_polymorphchicken', 10, 1, 10686, 7392, 0, 0, 10686, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(14, 8, 'Level Boost\r\n+20 Levels', 'Level Boost', '', 'Increase your characters levels by 20.', 'achievement_level_20', 20, 1, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(15, 5, 'Buff\r\nBlessing of Might', 'Buff', '', 'Buffs you with Blessing of Might.', 'spell_holy_fistofjustice', 1, 1, 0, 0, 0, 0, 27140, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(16, 9, 'Title\r\nChampion of the Naaru', 'Title', '', 'Gives you the title Champion of the Naaru.', 'inv_mace_51', 10, 1, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1),
|
||||
(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);
|
||||
(1, 8, 'Повышение уровня\r\n+10 уровней', 'Повышение уровня', '', 'Повышает уровень персонажа на 10.', 'achievement_level_10', 10, 1, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(2, 7, 'Смена фракции', 'Смена фракции', '', 'Позволяет сменить фракцию персонажа. Станет доступно после повторного входа.', 'vas_factionchange', 5, 1, 0, 0, 0, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(3, 7, 'Смена расы', 'Смена расы', '', 'Позволяет сменить расу персонажа. Станет доступно после повторного входа.', 'vas_racechange', 10, 1, 0, 0, 5, 0, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(4, 7, 'Смена имени', 'Смена имени', '', 'Позволяет сменить имя персонажа. Станет доступно после повторного входа.', 'vas_namechange', 5, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(5, 1, 'Игрушечный меч\r\n(двуручный)', '', 'item', '|cff00FFFFНажмите для предпросмотра|r', 'inv_sword_22', 10, 1, 45061, 45061, 0, 1, 45061, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(8, 3, 'Стремительный призрачный тигр\r\n(транспорт)', '', 'spell', '|cff00FFFFНажмите для предпросмотра|r', 'ability_mount_spectraltiger', 30, 1, 42777, 24004, 0, 0, 42777, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1),
|
||||
(12, 1, 'Эпическая лиловая рубашка', '', 'item', '|cff00FFFFНажмите для предпросмотра|r', 'inv_shirt_purple_01', 10, 1, 45037, 45037, 5, 1, 45037, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(13, 4, 'Луговая курица\r\n(питомец)', '', 'spell', '|cff00FFFFНажмите для предпросмотра|r', 'spell_magic_polymorphchicken', 10, 1, 10686, 7392, 0, 0, 10686, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(14, 8, 'Повышение уровня\r\n+20 уровней', 'Повышение уровня', '', 'Повышает уровень персонажа на 20.', 'achievement_level_20', 20, 1, 0, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(15, 5, 'Эффект\r\nБлагословение могущества', 'Эффект', '', 'Накладывает Благословение могущества.', 'spell_holy_fistofjustice', 1, 1, 0, 0, 0, 0, 27140, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1),
|
||||
(16, 9, 'Титул\r\nЗащитник Наару', 'Титул', '', 'Открывает титул «Защитник Наару».', 'inv_mace_51', 10, 1, 0, 0, 0, 0, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1),
|
||||
(17, 8, 'Повышение до 60-го уровня', 'Повышение уровня', '', 'Повышает уровень персонажа до 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, 'Комплект смокинга', 'Комплект смокинга', '', 'Стильный комплект смокинга.\r\n\r\nСодержит:\r\n\r\n1x Куртка-смокинг\r\n1x Рубашка под смокинг\r\n1x Брюки под смокинг', '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 visible texts in already initialized databases as well.
|
||||
UPDATE `store_services` SET
|
||||
`name` = CASE `id`
|
||||
WHEN 1 THEN 'Повышение уровня\r\n+10 уровней' WHEN 2 THEN 'Смена фракции'
|
||||
WHEN 3 THEN 'Смена расы' WHEN 4 THEN 'Смена имени'
|
||||
WHEN 5 THEN 'Игрушечный меч\r\n(двуручный)'
|
||||
WHEN 8 THEN 'Стремительный призрачный тигр\r\n(транспорт)'
|
||||
WHEN 12 THEN 'Эпическая лиловая рубашка' WHEN 13 THEN 'Луговая курица\r\n(питомец)'
|
||||
WHEN 14 THEN 'Повышение уровня\r\n+20 уровней'
|
||||
WHEN 15 THEN 'Эффект\r\nБлагословение могущества'
|
||||
WHEN 16 THEN 'Титул\r\nЗащитник Наару' WHEN 17 THEN 'Повышение до 60-го уровня'
|
||||
WHEN 18 THEN 'Комплект смокинга' ELSE `name` END,
|
||||
`tooltipName` = CASE `id`
|
||||
WHEN 1 THEN 'Повышение уровня' WHEN 2 THEN 'Смена фракции' WHEN 3 THEN 'Смена расы'
|
||||
WHEN 4 THEN 'Смена имени' WHEN 14 THEN 'Повышение уровня' WHEN 15 THEN 'Эффект'
|
||||
WHEN 16 THEN 'Титул' WHEN 17 THEN 'Повышение уровня' WHEN 18 THEN 'Комплект смокинга'
|
||||
ELSE `tooltipName` END,
|
||||
`tooltipText` = CASE `id`
|
||||
WHEN 1 THEN 'Повышает уровень персонажа на 10.'
|
||||
WHEN 2 THEN 'Позволяет сменить фракцию персонажа. Станет доступно после повторного входа.'
|
||||
WHEN 3 THEN 'Позволяет сменить расу персонажа. Станет доступно после повторного входа.'
|
||||
WHEN 4 THEN 'Позволяет сменить имя персонажа. Станет доступно после повторного входа.'
|
||||
WHEN 5 THEN '|cff00FFFFНажмите для предпросмотра|r'
|
||||
WHEN 8 THEN '|cff00FFFFНажмите для предпросмотра|r'
|
||||
WHEN 12 THEN '|cff00FFFFНажмите для предпросмотра|r'
|
||||
WHEN 13 THEN '|cff00FFFFНажмите для предпросмотра|r'
|
||||
WHEN 14 THEN 'Повышает уровень персонажа на 20.'
|
||||
WHEN 15 THEN 'Накладывает Благословение могущества.'
|
||||
WHEN 16 THEN 'Открывает титул «Защитник Наару».'
|
||||
WHEN 17 THEN 'Повышает уровень персонажа до 60-го.'
|
||||
WHEN 18 THEN 'Стильный комплект смокинга.\r\n\r\nСодержит:\r\n\r\n1x Куртка-смокинг\r\n1x Рубашка под смокинг\r\n1x Брюки под смокинг'
|
||||
ELSE `tooltipText` END
|
||||
WHERE `id` IN (1,2,3,4,5,8,12,13,14,15,16,17,18);
|
||||
|
||||
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, '') */;
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace lfg
|
||||
RANDOM_DUNGEON_HEROIC_WOTLK = 262
|
||||
};
|
||||
|
||||
constexpr uint32 LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET = 1000;
|
||||
|
||||
class Lfg5Guids;
|
||||
|
||||
typedef std::list<Lfg5Guids> Lfg5GuidsList;
|
||||
|
||||
@@ -519,6 +519,7 @@ namespace lfg
|
||||
LfgJoinResultData joinData;
|
||||
LfgGuidSet players;
|
||||
uint32 rDungeonId = 0;
|
||||
bool soloQueue = false;
|
||||
bool isContinue = grp && grp->isLFGGroup() && GetState(gguid) != LFG_STATE_FINISHED_DUNGEON;
|
||||
|
||||
if (grp && (grp->isBGGroup() || grp->isBFGroup()))
|
||||
@@ -611,6 +612,10 @@ namespace lfg
|
||||
}
|
||||
}
|
||||
|
||||
soloQueue = rDungeonId && IsSoloRandomDungeon(rDungeonId);
|
||||
if (soloQueue && grp)
|
||||
joinData.result = LFG_JOIN_PARTY_NOT_MEET_REQS;
|
||||
|
||||
if (!isRaid && joinData.result == LFG_JOIN_OK)
|
||||
{
|
||||
// Check player or group member restrictions
|
||||
@@ -775,7 +780,7 @@ namespace lfg
|
||||
LfgRolesMap rolesMap;
|
||||
rolesMap[guid] = roles;
|
||||
LFGQueue& queue = GetQueue(guid);
|
||||
queue.AddQueueData(guid, GameTime::GetGameTime().count(), dungeons, rolesMap);
|
||||
queue.AddQueueData(guid, GameTime::GetGameTime().count(), dungeons, rolesMap, soloQueue);
|
||||
|
||||
if (!isContinue)
|
||||
{
|
||||
@@ -1671,7 +1676,11 @@ namespace lfg
|
||||
}
|
||||
|
||||
// Xinef: Store amount of random players player grouped with
|
||||
if (group)
|
||||
LfgDungeonSet const& selectedDungeons = GetSelectedDungeons(pguid);
|
||||
bool const soloRandom = !selectedDungeons.empty() && IsSoloRandomDungeon(*selectedDungeons.begin());
|
||||
if (soloRandom)
|
||||
SetRandomPlayersCount(pguid, 0);
|
||||
else if (group)
|
||||
{
|
||||
SetRandomPlayersCount(pguid, group->GetMembersCount() >= MAXGROUPSIZE ? 0 : MAXGROUPSIZE - group->GetMembersCount());
|
||||
oldGroupGUID = group->GetGUID();
|
||||
@@ -2345,8 +2354,12 @@ namespace lfg
|
||||
*/
|
||||
LfgReward const* LFGMgr::GetRandomDungeonReward(uint32 dungeon, uint8 level)
|
||||
{
|
||||
uint32 dungeonId = dungeon & 0x00FFFFFF;
|
||||
if (IsSoloRandomDungeon(dungeonId))
|
||||
dungeonId -= LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET;
|
||||
|
||||
LfgReward const* rew = nullptr;
|
||||
LfgRewardContainerBounds bounds = RewardMapStore.equal_range(dungeon & 0x00FFFFFF);
|
||||
LfgRewardContainerBounds bounds = RewardMapStore.equal_range(dungeonId);
|
||||
for (LfgRewardContainer::const_iterator itr = bounds.first; itr != bounds.second; ++itr)
|
||||
{
|
||||
rew = itr->second;
|
||||
@@ -2358,6 +2371,20 @@ namespace lfg
|
||||
return rew;
|
||||
}
|
||||
|
||||
bool LFGMgr::IsSoloRandomDungeon(uint32 dungeon)
|
||||
{
|
||||
uint32 dungeonId = dungeon & 0x00FFFFFF;
|
||||
if (dungeonId < LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET)
|
||||
return false;
|
||||
|
||||
LFGDungeonData const* solo = GetLFGDungeon(dungeonId);
|
||||
LFGDungeonData const* regular = GetLFGDungeon(dungeonId - LFG_SOLO_RANDOM_DUNGEON_ID_OFFSET);
|
||||
return solo && regular &&
|
||||
solo->type == LFG_TYPE_RANDOM &&
|
||||
regular->type == LFG_TYPE_RANDOM &&
|
||||
solo->group == regular->group;
|
||||
}
|
||||
|
||||
/**
|
||||
Given a Dungeon id returns the dungeon Type
|
||||
|
||||
|
||||
@@ -525,6 +525,8 @@ namespace lfg
|
||||
bool IsDungeonDisabled(uint32 mapId, Difficulty difficulty) const;
|
||||
/// Gets the random dungeon reward corresponding to given dungeon and player level
|
||||
LfgReward const* GetRandomDungeonReward(uint32 dungeon, uint8 level);
|
||||
/// Checks whether an entry is a custom one-player random dungeon category
|
||||
bool IsSoloRandomDungeon(uint32 dungeon);
|
||||
/// Returns all random and seasonal dungeons for given level and expansion
|
||||
LfgDungeonSet GetRandomAndSeasonalDungeons(uint8 level, uint8 expansion);
|
||||
/// Teleport a player to/from selected dungeon
|
||||
|
||||
@@ -104,10 +104,10 @@ namespace lfg
|
||||
restoredAfterProposal.remove(guid);
|
||||
}
|
||||
|
||||
void LFGQueue::AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap)
|
||||
void LFGQueue::AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap, bool solo)
|
||||
{
|
||||
LOG_DEBUG("lfg", "JOINED AddQueueData: {}", guid.ToString());
|
||||
QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap);
|
||||
QueueDataStore[guid] = LfgQueueData(joinTime, dungeons, rolesMap, solo);
|
||||
AddToQueue(guid);
|
||||
}
|
||||
|
||||
@@ -263,6 +263,7 @@ namespace lfg
|
||||
// Check if more than one LFG group and number of players joining
|
||||
uint8 numPlayers = 0;
|
||||
uint8 numLfgGroups = 0;
|
||||
bool soloQueue = false;
|
||||
ObjectGuid guid;
|
||||
uint64 addToFoundMask = 0;
|
||||
|
||||
@@ -280,6 +281,7 @@ namespace lfg
|
||||
for (LfgRolesMap::const_iterator it2 = itQueue->second.roles.begin(); it2 != itQueue->second.roles.end(); ++it2)
|
||||
proposalGroups[it2->first] = itQueue->first.IsGroup() ? itQueue->first : ObjectGuid::Empty;
|
||||
|
||||
soloQueue = soloQueue || itQueue->second.solo;
|
||||
numPlayers += itQueue->second.roles.size();
|
||||
|
||||
if (sLFGMgr->IsLfgGroup(guid))
|
||||
@@ -293,8 +295,13 @@ namespace lfg
|
||||
if (numLfgGroups > 1)
|
||||
return LFG_INCOMPATIBLES_MULTIPLE_LFG_GROUPS;
|
||||
|
||||
// A solo RDF entry is a complete one-player match and must not absorb
|
||||
// entries from the regular five-player queue.
|
||||
if (soloQueue && (check.size() != 1 || numPlayers != 1))
|
||||
return LFG_INCOMPATIBLES_WRONG_GROUP_SIZE;
|
||||
|
||||
// Group with less that MAXGROUPSIZE members always compatible
|
||||
if (!sLFGMgr->IsTesting() && check.size() == 1 && numPlayers < MAXGROUPSIZE)
|
||||
if (!sLFGMgr->IsTesting() && !soloQueue && check.size() == 1 && numPlayers < MAXGROUPSIZE)
|
||||
{
|
||||
LfgQueueDataContainer::iterator itQueue = QueueDataStore.find(check.front());
|
||||
LfgRolesMap roles = itQueue->second.roles;
|
||||
@@ -388,10 +395,27 @@ namespace lfg
|
||||
proposalDungeons = queue.dungeons;
|
||||
proposalRoles = queue.roles;
|
||||
LFGMgr::CheckGroupRoles(proposalRoles); // assing new roles
|
||||
|
||||
// A one-player proposal still has to contain one of the three
|
||||
// canonical combat roles. The stock queue never reaches a
|
||||
// proposal with PLAYER_ROLE_NONE, while the solo queue can.
|
||||
// Sending NONE (or a multi-role mask) makes the 3.3.5a client
|
||||
// return "UNKNOWN" and LFDFrame.lua raises an error.
|
||||
if (soloQueue)
|
||||
{
|
||||
uint8& role = proposalRoles.begin()->second;
|
||||
role &= PLAYER_ROLE_TANK | PLAYER_ROLE_HEALER | PLAYER_ROLE_DAMAGE;
|
||||
if (role & PLAYER_ROLE_TANK)
|
||||
role = PLAYER_ROLE_TANK;
|
||||
else if (role & PLAYER_ROLE_HEALER)
|
||||
role = PLAYER_ROLE_HEALER;
|
||||
else
|
||||
role = PLAYER_ROLE_DAMAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// Enough players?
|
||||
if (!sLFGMgr->IsTesting() && numPlayers != MAXGROUPSIZE)
|
||||
if (!sLFGMgr->IsTesting() && !soloQueue && numPlayers != MAXGROUPSIZE)
|
||||
{
|
||||
strGuids.addRoles(proposalRoles);
|
||||
for (uint8 i = 0; i < 5 && check.guids[i]; ++i)
|
||||
|
||||
@@ -40,9 +40,9 @@ namespace lfg
|
||||
{
|
||||
LfgQueueData();
|
||||
|
||||
LfgQueueData(time_t _joinTime, LfgDungeonSet _dungeons, LfgRolesMap _roles):
|
||||
LfgQueueData(time_t _joinTime, LfgDungeonSet _dungeons, LfgRolesMap _roles, bool _solo):
|
||||
joinTime(_joinTime), lastRefreshTime(_joinTime), tanks(LFG_TANKS_NEEDED), healers(LFG_HEALERS_NEEDED),
|
||||
dps(LFG_DPS_NEEDED), dungeons(std::move(_dungeons)), roles(std::move(_roles))
|
||||
dps(LFG_DPS_NEEDED), dungeons(std::move(_dungeons)), roles(std::move(_roles)), solo(_solo)
|
||||
{ }
|
||||
|
||||
time_t joinTime; // Player queue join time (to calculate wait times)
|
||||
@@ -52,6 +52,7 @@ namespace lfg
|
||||
uint8 dps{LFG_DPS_NEEDED}; // Dps needed
|
||||
LfgDungeonSet dungeons; // Selected Player/Group Dungeon/s
|
||||
LfgRolesMap roles; // Selected Player Role/s
|
||||
bool solo{false}; // Match as a one-player RDF group
|
||||
Lfg5Guids bestCompatible; // Best compatible combination of people queued
|
||||
};
|
||||
|
||||
@@ -75,7 +76,7 @@ namespace lfg
|
||||
// Add/Remove from queue
|
||||
void AddToQueue(ObjectGuid guid, bool failedProposal = false);
|
||||
void RemoveFromQueue(ObjectGuid guid, bool partial = false); // xinef: partial remove, dont delete data from list!
|
||||
void AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap);
|
||||
void AddQueueData(ObjectGuid guid, time_t joinTime, LfgDungeonSet const& dungeons, LfgRolesMap const& rolesMap, bool solo = false);
|
||||
void RemoveQueueData(ObjectGuid guid);
|
||||
|
||||
// Update Timers (when proposal success)
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Add native solo RDF categories to a WotLK 3.3.5a LFGDungeons.dbc."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import struct
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
MAGIC = b"WDBC"
|
||||
FIELD_COUNT = 49
|
||||
RECORD_SIZE = FIELD_COUNT * 4
|
||||
SOLO_ID_OFFSET = 1000
|
||||
|
||||
SOLO_NAMES = {
|
||||
258: "Случайное одиночное подземелье: Классика",
|
||||
259: "Случайное одиночное подземелье: Burning Crusade",
|
||||
260: "Случайное одиночное героическое: Burning Crusade",
|
||||
261: "Случайное одиночное подземелье Lich King",
|
||||
262: "Случайное одиночное героическое подземелье Lich King",
|
||||
}
|
||||
|
||||
SOLO_DESCRIPTION = (
|
||||
"Случайное подземелье для одного игрока. "
|
||||
"Существа и боссы масштабируются системой AutoBalance."
|
||||
)
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Duplicate the five stock random dungeon rows as solo RDF categories."
|
||||
)
|
||||
parser.add_argument("source", type=Path, help="Source LFGDungeons.dbc")
|
||||
parser.add_argument("output", type=Path, help="Patched LFGDungeons.dbc")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def add_string(string_block: bytearray, value: str) -> int:
|
||||
encoded = value.encode("utf-8") + b"\0"
|
||||
offset = len(string_block)
|
||||
string_block.extend(encoded)
|
||||
return offset
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
raw = args.source.read_bytes()
|
||||
if len(raw) < 20 or raw[:4] != MAGIC:
|
||||
raise SystemExit(f"{args.source}: not a WDBC file")
|
||||
|
||||
record_count, field_count, record_size, string_size = struct.unpack_from("<4I", raw, 4)
|
||||
if field_count != FIELD_COUNT or record_size != RECORD_SIZE:
|
||||
raise SystemExit(
|
||||
f"{args.source}: unexpected layout "
|
||||
f"(fields={field_count}, record_size={record_size})"
|
||||
)
|
||||
|
||||
records_start = 20
|
||||
strings_start = records_start + record_count * record_size
|
||||
strings_end = strings_start + string_size
|
||||
if strings_end != len(raw):
|
||||
raise SystemExit(f"{args.source}: inconsistent WDBC size")
|
||||
|
||||
records: list[list[int]] = []
|
||||
source_records: dict[int, list[int]] = {}
|
||||
solo_ids = {base_id + SOLO_ID_OFFSET for base_id in SOLO_NAMES}
|
||||
|
||||
for index in range(record_count):
|
||||
offset = records_start + index * record_size
|
||||
record = list(struct.unpack_from(f"<{FIELD_COUNT}I", raw, offset))
|
||||
if record[0] in SOLO_NAMES:
|
||||
source_records[record[0]] = record
|
||||
if record[0] not in solo_ids:
|
||||
records.append(record)
|
||||
|
||||
missing = sorted(set(SOLO_NAMES) - set(source_records))
|
||||
if missing:
|
||||
raise SystemExit(f"{args.source}: missing stock random dungeon rows: {missing}")
|
||||
|
||||
string_block = bytearray(raw[strings_start:strings_end])
|
||||
for base_id, name in SOLO_NAMES.items():
|
||||
record = source_records[base_id].copy()
|
||||
record[0] = base_id + SOLO_ID_OFFSET
|
||||
|
||||
name_offset = add_string(string_block, name)
|
||||
description_offset = add_string(string_block, SOLO_DESCRIPTION)
|
||||
|
||||
# Fill every locale slot. This keeps the custom row readable regardless
|
||||
# of which locale column the client selects.
|
||||
record[1:17] = [name_offset] * 16
|
||||
record[32:48] = [description_offset] * 16
|
||||
records.append(record)
|
||||
|
||||
records.sort(key=lambda record: record[0])
|
||||
output = bytearray()
|
||||
output.extend(MAGIC)
|
||||
output.extend(struct.pack("<4I", len(records), FIELD_COUNT, RECORD_SIZE, len(string_block)))
|
||||
for record in records:
|
||||
output.extend(struct.pack(f"<{FIELD_COUNT}I", *record))
|
||||
output.extend(string_block)
|
||||
|
||||
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = args.output.with_suffix(args.output.suffix + ".tmp")
|
||||
temporary.write_bytes(output)
|
||||
temporary.replace(args.output)
|
||||
print(f"Wrote {args.output} with solo RDF entries 1258-1262")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user