скрытие магазина и механизм удаления гильдий
This commit is contained in:
+169
@@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
ENV_FILE="${ENV_FILE:-$ROOT_DIR/.env}"
|
||||||
|
GUILD_NAME=""
|
||||||
|
ASSUME_YES=0
|
||||||
|
|
||||||
|
if [[ -f "$ENV_FILE" ]]; then
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "$ENV_FILE"
|
||||||
|
set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: $(basename "$0") <guild name> [--yes]
|
||||||
|
|
||||||
|
Permanently deletes a guild and its related character-database records.
|
||||||
|
The worldserver must be stopped before running this script.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--yes Skip the interactive confirmation
|
||||||
|
-h, --help Show this help
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
docker compose stop ac-worldserver
|
||||||
|
./delete-guild.sh "Название гильдии"
|
||||||
|
./delete-guild.sh "Название гильдии" --yes
|
||||||
|
docker compose start ac-worldserver
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
log() {
|
||||||
|
printf '[delete-guild] %s\n' "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
while (($#)); do
|
||||||
|
case "$1" in
|
||||||
|
--yes)
|
||||||
|
ASSUME_YES=1
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
printf 'Unknown option: %s\n\n' "$1" >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ -n "$GUILD_NAME" ]]; then
|
||||||
|
printf 'Only one guild name may be specified\n\n' >&2
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
GUILD_NAME="$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -z "$GUILD_NAME" ]]; then
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$GUILD_NAME" == *$'\n'* || "$GUILD_NAME" == *$'\r'* || "$GUILD_NAME" == *$'\t'* ]]; then
|
||||||
|
printf 'Guild name must not contain control characters\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
RUNNING_SERVICES="$(docker compose ps --status running --services)"
|
||||||
|
|
||||||
|
if grep -qx 'ac-worldserver' <<<"$RUNNING_SERVICES"; then
|
||||||
|
printf 'ac-worldserver is running. Stop it first:\n docker compose stop ac-worldserver\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! grep -qx 'ac-database' <<<"$RUNNING_SERVICES"; then
|
||||||
|
printf 'ac-database is not running. Start it first:\n docker compose up -d ac-database\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
export ACORE_DELETE_GUILD_NAME="$GUILD_NAME"
|
||||||
|
SQL_GUILD_NAME="$(python3 <<'PY'
|
||||||
|
import os
|
||||||
|
|
||||||
|
value = os.environ["ACORE_DELETE_GUILD_NAME"]
|
||||||
|
print("'" + value.replace("\\", "\\\\").replace("'", "\\'") + "'")
|
||||||
|
PY
|
||||||
|
)"
|
||||||
|
|
||||||
|
GUILD_ROW="$(
|
||||||
|
docker compose exec -T ac-database bash -lc \
|
||||||
|
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --batch --skip-column-names acore_characters' \
|
||||||
|
<<<"SELECT guildid, name, leaderguid FROM guild WHERE name = ${SQL_GUILD_NAME} LIMIT 1;"
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [[ -z "$GUILD_ROW" ]]; then
|
||||||
|
printf 'Guild not found: %s\n' "$GUILD_NAME" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=$'\t' read -r GUILD_ID FOUND_NAME LEADER_GUID <<<"$GUILD_ROW"
|
||||||
|
log "found guild id=${GUILD_ID}, name=${FOUND_NAME}, leader=${LEADER_GUID}"
|
||||||
|
|
||||||
|
if ((ASSUME_YES == 0)); then
|
||||||
|
printf 'This permanently deletes the guild and all items in its bank.\n'
|
||||||
|
printf 'Type the guild name to confirm: '
|
||||||
|
read -r CONFIRMATION
|
||||||
|
if [[ "$CONFIRMATION" != "$FOUND_NAME" ]]; then
|
||||||
|
printf 'Confirmation did not match; nothing was deleted.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
SQL=$(cat <<EOF
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
SET @guild_id := ${GUILD_ID};
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE delete_guild_members (
|
||||||
|
guid INT UNSIGNED NOT NULL PRIMARY KEY
|
||||||
|
) ENGINE=MEMORY
|
||||||
|
SELECT guid FROM guild_member WHERE guildid = @guild_id;
|
||||||
|
|
||||||
|
CREATE TEMPORARY TABLE delete_guild_bank_items (
|
||||||
|
item_guid INT UNSIGNED NOT NULL PRIMARY KEY
|
||||||
|
) ENGINE=MEMORY
|
||||||
|
SELECT item_guid FROM guild_bank_item WHERE guildid = @guild_id;
|
||||||
|
|
||||||
|
DELETE item_instance
|
||||||
|
FROM item_instance
|
||||||
|
INNER JOIN delete_guild_bank_items
|
||||||
|
ON delete_guild_bank_items.item_guid = item_instance.guid;
|
||||||
|
|
||||||
|
DELETE guild_member_withdraw
|
||||||
|
FROM guild_member_withdraw
|
||||||
|
INNER JOIN delete_guild_members
|
||||||
|
ON delete_guild_members.guid = guild_member_withdraw.guid;
|
||||||
|
|
||||||
|
DELETE FROM guild_bank_eventlog WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_eventlog WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_bank_right WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_bank_item WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_bank_tab WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_rank WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild_member WHERE guildid = @guild_id;
|
||||||
|
DELETE FROM guild WHERE guildid = @guild_id;
|
||||||
|
|
||||||
|
SET @deleted := ROW_COUNT();
|
||||||
|
COMMIT;
|
||||||
|
|
||||||
|
SELECT @guild_id AS guildid, @deleted AS deleted;
|
||||||
|
EOF
|
||||||
|
)
|
||||||
|
|
||||||
|
log "deleting guild ${FOUND_NAME} (${GUILD_ID})"
|
||||||
|
docker compose exec -T ac-database bash -lc \
|
||||||
|
'mysql -uroot -p"$MYSQL_ROOT_PASSWORD" --show-warnings acore_characters' <<<"$SQL"
|
||||||
|
|
||||||
|
log "guild deleted"
|
||||||
-1715993
File diff suppressed because it is too large
Load Diff
+17
-5
@@ -184,6 +184,23 @@ import_store_sql() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if ! database_exists "store"; then
|
||||||
|
log "creating database store"
|
||||||
|
mysql_exec "" "CREATE DATABASE IF NOT EXISTS \`store\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Unlike the rest of Store.sql, this is a repeatable migration. It ensures
|
||||||
|
# existing store databases receive the global switch without overwriting a
|
||||||
|
# value later changed by an administrator.
|
||||||
|
mysql_exec "store" "
|
||||||
|
CREATE TABLE IF NOT EXISTS \`store_config\` (
|
||||||
|
\`id\` TINYINT UNSIGNED NOT NULL,
|
||||||
|
\`enabled\` TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||||
|
PRIMARY KEY (\`id\`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
INSERT IGNORE INTO \`store_config\` (\`id\`, \`enabled\`) VALUES (1, 0);
|
||||||
|
"
|
||||||
|
|
||||||
# Store.sql is a one-time bootstrap, not a repeatable migration. It
|
# Store.sql is a one-time bootstrap, not a repeatable migration. It
|
||||||
# contains UPDATE and DELETE statements that would overwrite values
|
# contains UPDATE and DELETE statements that would overwrite values
|
||||||
# maintained in the live store database. The canonical services table is
|
# maintained in the live store database. The canonical services table is
|
||||||
@@ -194,11 +211,6 @@ import_store_sql() {
|
|||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! database_exists "store"; then
|
|
||||||
log "creating database store"
|
|
||||||
mysql_exec "" "CREATE DATABASE IF NOT EXISTS \`store\` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"
|
|
||||||
fi
|
|
||||||
|
|
||||||
temp_sql="$(mktemp)"
|
temp_sql="$(mktemp)"
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,7 +87,14 @@ end
|
|||||||
|
|
||||||
local StoreHandler = AIO.AddHandlers("STORE_CLIENT", {})
|
local StoreHandler = AIO.AddHandlers("STORE_CLIENT", {})
|
||||||
|
|
||||||
function StoreHandler.FrameData(player, services, links, nav, currencies, rank)
|
function StoreHandler.FrameData(player, storeEnabled, services, links, nav, currencies, rank)
|
||||||
|
SHOP_UI["Vars"].storeEnabled = storeEnabled == 1
|
||||||
|
SHOP_UI.StoreAvailability_Update()
|
||||||
|
|
||||||
|
if not SHOP_UI["Vars"].storeEnabled then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
SHOP_UI["Data"].services = services
|
SHOP_UI["Data"].services = services
|
||||||
SHOP_UI["Data"].links = links
|
SHOP_UI["Data"].links = links
|
||||||
SHOP_UI["Data"].nav = nav
|
SHOP_UI["Data"].nav = nav
|
||||||
@@ -113,6 +120,7 @@ SHOP_UI = {
|
|||||||
currentPage = 1,
|
currentPage = 1,
|
||||||
maxPages = 1,
|
maxPages = 1,
|
||||||
accountRank = 0,
|
accountRank = 0,
|
||||||
|
storeEnabled = false,
|
||||||
["playerCurrencies"] = {}
|
["playerCurrencies"] = {}
|
||||||
},
|
},
|
||||||
["Data"] = {
|
["Data"] = {
|
||||||
@@ -146,6 +154,12 @@ function SHOP_UI.MainFrame_Create()
|
|||||||
shopFrame.Title:SetShadowOffset(1, -1)
|
shopFrame.Title:SetShadowOffset(1, -1)
|
||||||
shopFrame.Title:SetPoint("TOP", shopFrame, "TOP", 0, -3)
|
shopFrame.Title:SetPoint("TOP", shopFrame, "TOP", 0, -3)
|
||||||
shopFrame.Title:SetText("|cffedd100Лавка Лунной Жрицы|r")
|
shopFrame.Title:SetText("|cffedd100Лавка Лунной Жрицы|r")
|
||||||
|
|
||||||
|
shopFrame.UnavailableText = shopFrame:CreateFontString()
|
||||||
|
shopFrame.UnavailableText:SetFont("Fonts\\FRIZQT__.TTF", 24, "OUTLINE")
|
||||||
|
shopFrame.UnavailableText:SetShadowOffset(1, -1)
|
||||||
|
shopFrame.UnavailableText:SetPoint("CENTER", shopFrame, "CENTER", 85, 0)
|
||||||
|
shopFrame.UnavailableText:SetText("|cffedd100Извините, магазин ещё не работает|r")
|
||||||
|
|
||||||
-- create navigation button placeholders, pass parent as arg
|
-- create navigation button placeholders, pass parent as arg
|
||||||
SHOP_UI.NavButtons_Create(shopFrame)
|
SHOP_UI.NavButtons_Create(shopFrame)
|
||||||
@@ -189,6 +203,7 @@ function SHOP_UI.MainFrame_Create()
|
|||||||
shopFrame:SetScript(
|
shopFrame:SetScript(
|
||||||
"OnShow",
|
"OnShow",
|
||||||
function()
|
function()
|
||||||
|
AIO.Handle("STORE_SERVER", "FrameData")
|
||||||
AIO.Handle("STORE_SERVER", "UpdateCurrencies")
|
AIO.Handle("STORE_SERVER", "UpdateCurrencies")
|
||||||
PlaySound("AuctionWindowOpen", "Master")
|
PlaySound("AuctionWindowOpen", "Master")
|
||||||
end
|
end
|
||||||
@@ -207,6 +222,32 @@ function SHOP_UI.MainFrame_Create()
|
|||||||
tinsert(UISpecialFrames, shopFrame:GetName())
|
tinsert(UISpecialFrames, shopFrame:GetName())
|
||||||
|
|
||||||
SHOP_UI["FRAME"] = shopFrame
|
SHOP_UI["FRAME"] = shopFrame
|
||||||
|
SHOP_UI.StoreAvailability_Update()
|
||||||
|
end
|
||||||
|
|
||||||
|
function SHOP_UI.StoreAvailability_Update()
|
||||||
|
local enabled = SHOP_UI["Vars"].storeEnabled
|
||||||
|
|
||||||
|
if SHOP_UI["FRAME"] and SHOP_UI["FRAME"].UnavailableText then
|
||||||
|
if enabled then
|
||||||
|
SHOP_UI["FRAME"].UnavailableText:Hide()
|
||||||
|
else
|
||||||
|
SHOP_UI["FRAME"].UnavailableText:Show()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, collectionName in pairs({"NAV_BUTTONS", "SERVICE_BUTTONS", "PAGING_ELEMENTS", "CURRENCY_BUTTONS"}) do
|
||||||
|
local collection = SHOP_UI[collectionName]
|
||||||
|
if collection and not enabled then
|
||||||
|
for _, element in pairs(collection) do
|
||||||
|
element:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if SHOP_UI["MODEL_FRAME"] and not enabled then
|
||||||
|
SHOP_UI["MODEL_FRAME"]:Hide()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- create navigation button placeholders
|
-- create navigation button placeholders
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ local CONFIG = {
|
|||||||
tooHighLevel = "Уровень персонажа слишком высок",
|
tooHighLevel = "Уровень персонажа слишком высок",
|
||||||
mailBody = "Благодарим за покупку в Лавке Лунной Жрицы!",
|
mailBody = "Благодарим за покупку в Лавке Лунной Жрицы!",
|
||||||
-- The service name is prefixed to this message
|
-- The service name is prefixed to this message
|
||||||
successfulPurchase = "Покупка успешно совершена!"
|
successfulPurchase = "Покупка успешно совершена!",
|
||||||
|
storeUnavailable = "Магазин пока не работает"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,11 +61,31 @@ local KEYS = GetDataStructKeys();
|
|||||||
|
|
||||||
local StoreHandler = AIO.AddHandlers("STORE_SERVER", {})
|
local StoreHandler = AIO.AddHandlers("STORE_SERVER", {})
|
||||||
|
|
||||||
|
local function IsStoreEnabled()
|
||||||
|
local query = WorldDBQuery("SELECT `enabled` FROM `store`.`store_config` WHERE `id` = 1 LIMIT 1;")
|
||||||
|
return query and query:GetUInt32(0) == 1
|
||||||
|
end
|
||||||
|
|
||||||
function StoreHandler.FrameData(player)
|
function StoreHandler.FrameData(player)
|
||||||
AIO.Handle(player, "STORE_CLIENT", "FrameData", GetServiceData(), GetLinkData(), GetNavData(), GetCurrencyData(), player:GetGMRank())
|
local enabled = IsStoreEnabled()
|
||||||
|
AIO.Handle(
|
||||||
|
player,
|
||||||
|
"STORE_CLIENT",
|
||||||
|
"FrameData",
|
||||||
|
enabled and 1 or 0,
|
||||||
|
enabled and GetServiceData() or {},
|
||||||
|
enabled and GetLinkData() or {},
|
||||||
|
enabled and GetNavData() or {},
|
||||||
|
enabled and GetCurrencyData() or {},
|
||||||
|
player:GetGMRank()
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
function StoreHandler.UpdateCurrencies(player)
|
function StoreHandler.UpdateCurrencies(player)
|
||||||
|
if not IsStoreEnabled() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local tmp = {}
|
local tmp = {}
|
||||||
for currencyId, currency in pairs(GetCurrencyData()) do
|
for currencyId, currency in pairs(GetCurrencyData()) do
|
||||||
local val = 0
|
local val = 0
|
||||||
@@ -87,6 +108,11 @@ function StoreHandler.UpdateCurrencies(player)
|
|||||||
end
|
end
|
||||||
|
|
||||||
function StoreHandler.Purchase(player, serviceId)
|
function StoreHandler.Purchase(player, serviceId)
|
||||||
|
if not IsStoreEnabled() then
|
||||||
|
player:SendAreaTriggerMessage(CONFIG.strings.storeUnavailable)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local services = GetServiceData()
|
local services = GetServiceData()
|
||||||
|
|
||||||
-- See if the requested service exists
|
-- See if the requested service exists
|
||||||
|
|||||||
@@ -14,6 +14,17 @@
|
|||||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||||
|
|
||||||
|
-- Global store switch. Keep the initial value disabled until the storefront is
|
||||||
|
-- ready. Existing installations preserve their current value on re-import.
|
||||||
|
CREATE TABLE IF NOT EXISTS `store_config` (
|
||||||
|
`id` tinyint unsigned NOT NULL,
|
||||||
|
`enabled` tinyint unsigned NOT NULL DEFAULT '0',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
|
||||||
|
|
||||||
|
INSERT INTO `store_config` (`id`, `enabled`) VALUES (1, 0)
|
||||||
|
ON DUPLICATE KEY UPDATE `id` = VALUES(`id`);
|
||||||
|
|
||||||
-- Dumping structure for table store.store_categories
|
-- Dumping structure for table store.store_categories
|
||||||
CREATE TABLE IF NOT EXISTS `store_categories` (
|
CREATE TABLE IF NOT EXISTS `store_categories` (
|
||||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
|||||||
Reference in New Issue
Block a user