From b5f3f512083707de8b0ec162a5548a82f54fe888 Mon Sep 17 00:00:00 2001 From: gasaichandesu Date: Mon, 10 Aug 2026 17:40:02 +0400 Subject: [PATCH 1/4] feat(premium): implement premium system through ACCOUNT_FLAG_RECURRING_BILLING flag --- lua_scripts/MWPremium/config.lua | 1 + lua_scripts/MWPremium/mwpremium.lua | 91 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 lua_scripts/MWPremium/config.lua create mode 100644 lua_scripts/MWPremium/mwpremium.lua diff --git a/lua_scripts/MWPremium/config.lua b/lua_scripts/MWPremium/config.lua new file mode 100644 index 000000000..a56470754 --- /dev/null +++ b/lua_scripts/MWPremium/config.lua @@ -0,0 +1 @@ +return {} diff --git a/lua_scripts/MWPremium/mwpremium.lua b/lua_scripts/MWPremium/mwpremium.lua new file mode 100644 index 000000000..462e63d7e --- /dev/null +++ b/lua_scripts/MWPremium/mwpremium.lua @@ -0,0 +1,91 @@ +local PLAYER_EVENT_ON_COMMAND = 42 +local PLAYER_EVENT_ON_LOGIN = 3 +local PREMIUM_FLAG_INDEX = 100 +local SUMMON_COMPANION_SPELL_ID = 9000 +local ACCOUNT_FLAG_RECURRING_BILLING = 8192 + +---todo extract to utils +---@param inputstr string +---@param separator string +---@return table +local function SplitString(inputstr, separator) + if separator == nil then + separator = "%s" + end + local t = {} + for str in string.gmatch(inputstr, "([^" .. separator .. "]+)") do + table.insert(t, str) + end + return t +end + +---@return boolean +local function IsPremium(player) + return player:HasFlag(PREMIUM_FLAG_INDEX) +end + +local function OnPremiumPlayer(player) + if not IsPremium(player) then + return + end + + player:SendBroadcastMessage('На вашем аккаунте активна подписка Premium! Благодарим за поддержку проекта MoonWell.') +end + +local function RefreshPremiumStatus(player) + local query = AuthDBQuery("SELECT flags FROM account WHERE id = " .. player:GetAccountId()) + + if query then + local function HasFlag(mask, flag) + return bit.band(mask, flag) == flag + end + + + local flags = query:GetUInt32(0) + + if HasFlag(flags, ACCOUNT_FLAG_RECURRING_BILLING) then + player:SetFlag(PREMIUM_FLAG_INDEX, 1) + + OnPremiumPlayer(player) + + return true + end + end + + + return false +end + +local function ApplyPremium(player) + AuthDBExecute( + string.format( + "UPDATE account SET flags = flags | %d WHERE id = %d", + ACCOUNT_FLAG_RECURRING_BILLING, + player:GetAccountId() + ) + ) + + OnPremiumPlayer(player) +end + +--- @param event any +--- @param player any +--- @param command string +--- @param chatHandler any +local function OnCommand(event, player, command, chatHandler) + ---Console is not supported yet + if not player then return end + + local parts = SplitString(command:lower(), ' ') + + if parts[1] ~= 'premium' then return end + + if parts[2] == 'add' then + ApplyPremium(player:GetSelection()) + end +end + + + +RegsiterPlayerEvent(PLAYER_EVENT_ON_LOGIN, RefreshPremiumStatus) +RegisterPlayerEvent(PLAYER_EVENT_ON_COMMAND, OnCommand) -- 2.52.0 From 9d7bfe251694c6deb124c822bff1eccbb4b4fd47 Mon Sep 17 00:00:00 2001 From: gasaichan Date: Mon, 10 Aug 2026 16:14:18 +0200 Subject: [PATCH 2/4] feat(premium): fix premium script startup errors --- lua_scripts/MWPremium/mwpremium.lua | 16 +++++++++++----- modules/mod-eluna/conf/mod_eluna.conf.dist | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lua_scripts/MWPremium/mwpremium.lua b/lua_scripts/MWPremium/mwpremium.lua index 462e63d7e..ff33da101 100644 --- a/lua_scripts/MWPremium/mwpremium.lua +++ b/lua_scripts/MWPremium/mwpremium.lua @@ -21,7 +21,7 @@ end ---@return boolean local function IsPremium(player) - return player:HasFlag(PREMIUM_FLAG_INDEX) + return player:HasFlag(PREMIUM_FLAG_INDEX, 1) end local function OnPremiumPlayer(player) @@ -29,7 +29,7 @@ local function OnPremiumPlayer(player) return end - player:SendBroadcastMessage('На вашем аккаунте активна подписка Premium! Благодарим за поддержку проекта MoonWell.') + player:SendAreaTriggerMessage('На вашем аккаунте активна подписка Premium! Благодарим за поддержку проекта MoonWell.') end local function RefreshPremiumStatus(player) @@ -37,7 +37,7 @@ local function RefreshPremiumStatus(player) if query then local function HasFlag(mask, flag) - return bit.band(mask, flag) == flag + return bit_and(mask, flag) == flag end @@ -65,7 +65,11 @@ local function ApplyPremium(player) ) ) - OnPremiumPlayer(player) + RefreshPremiumStatus(player) +end + +local function OnLogin(event, player) + RefreshPremiumStatus(player) end --- @param event any @@ -83,9 +87,11 @@ local function OnCommand(event, player, command, chatHandler) if parts[2] == 'add' then ApplyPremium(player:GetSelection()) end + + return false end -RegsiterPlayerEvent(PLAYER_EVENT_ON_LOGIN, RefreshPremiumStatus) +RegisterPlayerEvent(PLAYER_EVENT_ON_LOGIN, OnLogin) RegisterPlayerEvent(PLAYER_EVENT_ON_COMMAND, OnCommand) diff --git a/modules/mod-eluna/conf/mod_eluna.conf.dist b/modules/mod-eluna/conf/mod_eluna.conf.dist index 2047d0246..9cc1bc9dc 100644 --- a/modules/mod-eluna/conf/mod_eluna.conf.dist +++ b/modules/mod-eluna/conf/mod_eluna.conf.dist @@ -61,9 +61,9 @@ # false - (disabled) Eluna.Enabled = true -Eluna.TraceBack = false +Eluna.TraceBack = true Eluna.ScriptPath = "lua_scripts" -Eluna.PlayerAnnounceReload = false +Eluna.PlayerAnnounceReload = true Eluna.RequirePaths = "" Eluna.RequireCPaths = "" Eluna.AutoReload = false -- 2.52.0 From e04f0f068c685b6a79bccdf58bb045ccae03b643 Mon Sep 17 00:00:00 2001 From: gasaichan Date: Wed, 19 Aug 2026 12:43:32 +0200 Subject: [PATCH 3/4] feat(premium): implement summon squire spell interceptor Squire summon spell is intercepted to summon custom NPC with "service" dialogue options such as open mail, open bank, etc. --- lua_scripts/MWPremium/mwpremium.lua | 81 +++++++++++++++++++++++++++-- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/lua_scripts/MWPremium/mwpremium.lua b/lua_scripts/MWPremium/mwpremium.lua index ff33da101..5600f9d97 100644 --- a/lua_scripts/MWPremium/mwpremium.lua +++ b/lua_scripts/MWPremium/mwpremium.lua @@ -1,9 +1,18 @@ -local PLAYER_EVENT_ON_COMMAND = 42 local PLAYER_EVENT_ON_LOGIN = 3 +local PLAYER_EVENT_ON_SPELL_CAST = 5 +local PLAYER_EVENT_ON_LOGOUT = 4 +local PLAYER_EVENT_ON_COMMAND = 42 + local PREMIUM_FLAG_INDEX = 100 -local SUMMON_COMPANION_SPELL_ID = 9000 + local ACCOUNT_FLAG_RECURRING_BILLING = 8192 +local SUMMON_SQUIRE_SPELL_ID = 80000 + +local SQUIRE_CREATURE_TEMPLATE_ID = 9000001 + +local NPC_TEXT_ID = 10000000 + ---todo extract to utils ---@param inputstr string ---@param separator string @@ -29,7 +38,12 @@ local function OnPremiumPlayer(player) return end - player:SendAreaTriggerMessage('На вашем аккаунте активна подписка Premium! Благодарим за поддержку проекта MoonWell.') + player:SendAreaTriggerMessage( + 'На вашем аккаунте активна подписка Premium! Благодарим за поддержку проекта MoonWell.') + + player:LearnSpell(SUMMON_SQUIRE_SPELL_ID) + player:SendBroadcastMessage("Милостью Элуны вам была выдана способность |cff71d5ff|Hspell:" .. + SUMMON_SQUIRE_SPELL_ID .. "|h[" .. "Призыв Оруженосца]h|") end local function RefreshPremiumStatus(player) @@ -91,7 +105,68 @@ local function OnCommand(event, player, command, chatHandler) return false end +local function OnSummonSquireCast(player) + local x, y, z, o = player:GetLocation() + local squire = player:SpawnCreature(SQUIRE_CREATURE_TEMPLATE_ID, x + 1, y + 1, z, o, 1, 30000) + if squire then + squire:SetFaction(player:GetFaction()) + end +end + +local function OnSpellCast(event, player, spell, skipCheck) + local spellId = spell:GetEntry() + + if spellId == SUMMON_SQUIRE_SPELL_ID then + OnSummonSquireCast(player) + end +end + +local function OnGossipHello(event, player, creature) + player:GossipClearMenu() + + player:GossipMenuAddItem(4, "Открыть почтовый ящик", 900001, 1) + player:GossipMenuAddItem(1, "Открыть банк", 900001, 2) + player:GossipMenuAddItem(6, "Открыть аукцион", 900001, 3) + player:GossipMenuAddItem(4, "Отремонтировать снаряжение", 900001, 4) + + player:GossipSendMenu(NPC_TEXT_ID, creature) +end + +local function OnGossipSelect(event, player, creature, sender, intid, code) + if sender == 900001 then + if intid == 1 then + player:SendShowMailBox(creature:GetGUID()) + player:GossipComplete() + end + + if intid == 2 then + player:SendShowBank(creature) + player:GossipComplete() + end + + if intid == 3 then + player:SendAuctionMenu(creature) + player:GossipComplete() + end + + if intid == 4 then + player:DurabilityRepairAll() + player:PlayDirectSound(1116) + player:GossipComplete() + end + end +end + +local function OnLogout(event, player) + player:RemoveSpell(SUMMON_SQUIRE_SPELL_ID) +end + +RegisterCreatureGossipEvent(SQUIRE_CREATURE_TEMPLATE_ID, 1, OnGossipHello) +RegisterCreatureGossipEvent(SQUIRE_CREATURE_TEMPLATE_ID, 2, OnGossipSelect) RegisterPlayerEvent(PLAYER_EVENT_ON_LOGIN, OnLogin) +RegisterPlayerEvent(PLAYER_EVENT_ON_LOGOUT, OnLogout) RegisterPlayerEvent(PLAYER_EVENT_ON_COMMAND, OnCommand) +RegisterPlayerEvent(PLAYER_EVENT_ON_SPELL_CAST, OnSpellCast) + -- 2.52.0 From 5918cf7b1ab551872bf7b67383a4b73987531968 Mon Sep 17 00:00:00 2001 From: gasaichandesu Date: Wed, 19 Aug 2026 16:50:58 +0400 Subject: [PATCH 4/4] feat(premium): add SQL data for premium feature --- import-custom-sql.sh | 9 + sql/Premium/world.sql | 382 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 391 insertions(+) create mode 100644 sql/Premium/world.sql diff --git a/import-custom-sql.sh b/import-custom-sql.sh index e98d51583..21e8298ad 100644 --- a/import-custom-sql.sh +++ b/import-custom-sql.sh @@ -341,6 +341,14 @@ import_encounter_journal_sql() { fi } +import_premium_sql() { + log "Started importing Premium SQL files" + + local world_premium_sql="$ROOT_DIR/sql/Premium/world.sql" + + import_sql_file "acore_world" "$world_premium_sql" +} + if (($# > 0)); then case "$1" in -h|--help) @@ -373,6 +381,7 @@ import_playerbots_ru_sql import_encounter_journal_sql import_store_sql import_transmog_sql +import_premium_sql configure_server_motd log "custom SQL bootstrap completed" diff --git a/sql/Premium/world.sql b/sql/Premium/world.sql new file mode 100644 index 000000000..663a86a88 --- /dev/null +++ b/sql/Premium/world.sql @@ -0,0 +1,382 @@ +-- Create Squire NPC +INSERT + INTO + acore_world.creature_template (entry, + difficulty_entry_1, + difficulty_entry_2, + difficulty_entry_3, + KillCredit1, + KillCredit2, + name, + subname, + IconName, + gossip_menu_id, + minlevel, + maxlevel, + `exp`, + faction, + npcflag, + speed_walk, + speed_run, + speed_swim, + speed_flight, + detection_range, + `scale`, + `rank`, + dmgschool, + DamageModifier, + BaseAttackTime, + RangeAttackTime, + BaseVariance, + RangeVariance, + unit_class, + unit_flags, + unit_flags2, + dynamicflags, + family, + `type`, + type_flags, + lootid, + pickpocketloot, + skinloot, + PetSpellDataId, + VehicleId, + mingold, + maxgold, + AIName, + MovementType, + HoverHeight, + HealthModifier, + ManaModifier, + ArmorModifier, + ExperienceModifier, + RacialLeader, + movementId, + RegenHealth, + mechanic_immune_mask, + spell_school_immune_mask, + flags_extra, + ScriptName, + VerifiedBuild) +VALUES + (9000001, +29831, +0, +0, +0, +0, +'Eluna Squire', +'', +'', +0, +81, +81, +2, +21, +1, +1.0, +1.42857, +1.0, +1.0, +20.0, +1.0, +1, +0, +7.5, +2000, +2000, +1.0, +1.0, +1, +64, +2048, +8, +0, +6, +72, +100003, +0, +0, +0, +0, +1163, +1524, +'SmartAI', +0, +1.0, +4.0, +1.0, +1.0, +2.5, +0, +144, +1, +1007366674, +0, +0, +'', +12340) ON DUPLICATE KEY UPDATE entry = entry; + + +-- Add Squire localization +INSERT INTO + acore_world.creature_template_locale (entry, + locale, + Name, + Title, + VerifiedBuild) +VALUES + (9000001, +'ruRU', +'Оруженосец', +'', +12340) ON DUPLICATE KEY UPDATE entry = entry; + + +-- Add Squire model +INSERT + INTO + acore_world.creature_template_model (CreatureID, + Idx, + CreatureDisplayID, + DisplayScale, + Probability, + VerifiedBuild) +VALUES + (9000001, +0, +16927, +1.0, +1.0, +12340) ON +DUPLICATE KEY +UPDATE + CreatureID = CreatureID; + +-- Add Squire dialogue text +INSERT + INTO + acore_world.npc_text (ID, + text0_0, + text0_1, + BroadcastTextID0, + lang0, + Probability0, + em0_0, + em0_1, + em0_2, + em0_3, + em0_4, + em0_5, + text1_0, + text1_1, + BroadcastTextID1, + lang1, + Probability1, + em1_0, + em1_1, + em1_2, + em1_3, + em1_4, + em1_5, + text2_0, + text2_1, + BroadcastTextID2, + lang2, + Probability2, + em2_0, + em2_1, + em2_2, + em2_3, + em2_4, + em2_5, + text3_0, + text3_1, + BroadcastTextID3, + lang3, + Probability3, + em3_0, + em3_1, + em3_2, + em3_3, + em3_4, + em3_5, + text4_0, + text4_1, + BroadcastTextID4, + lang4, + Probability4, + em4_0, + em4_1, + em4_2, + em4_3, + em4_4, + em4_5, + text5_0, + text5_1, + BroadcastTextID5, + lang5, + Probability5, + em5_0, + em5_1, + em5_2, + em5_3, + em5_4, + em5_5, + text6_0, + text6_1, + BroadcastTextID6, + lang6, + Probability6, + em6_0, + em6_1, + em6_2, + em6_3, + em6_4, + em6_5, + text7_0, + text7_1, + BroadcastTextID7, + lang7, + Probability7, + em7_0, + em7_1, + em7_2, + em7_3, + em7_4, + em7_5, + VerifiedBuild) +VALUES + (10000000, +'', +'By the grace of the stars, I have been bound to your side, $n. This ancient pile of bones is ready to serve you.', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +'', +'', +0, +0, +0.0, +0, +0, +0, +0, +0, +0, +0) ON DUPLICATE KEY UPDATE ID = ID; + +-- Add squire dialogue text locale +INSERT + INTO + acore_world.npc_text_locale (ID, + Locale, + Text0_0, + Text0_1, + Text1_0, + Text1_1, + Text2_0, + Text2_1, + Text3_0, + Text3_1, + Text4_0, + Text4_1, + Text5_0, + Text5_1, + Text6_0, + Text6_1, + Text7_0, + Text7_1) +VALUES + (10000000, +'ruRU', +'', +'Милостью высших сил я был приставлен к тебе, $n. Эти древние кости готовы служить. Чего желаешь?', +'', +'', +'', +'', +'', +'', +'', +'', +'', +'', +'', +'', +'', +'') ON DUPLICATE KEY UPDATE ID = ID; + + -- 2.52.0