local ____lualib = require("lualib_bundle") local __TS__Class = ____lualib.__TS__Class local Set = ____lualib.Set local __TS__New = ____lualib.__TS__New local __TS__ArrayFind = ____lualib.__TS__ArrayFind local __TS__ArrayForEach = ____lualib.__TS__ArrayForEach local __TS__ArrayFilter = ____lualib.__TS__ArrayFilter local __TS__ArrayFrom = ____lualib.__TS__ArrayFrom local ____exports = {} local ____entity_radius = require("utils.entity_radius") local findAllByClassnameInRadius = ____entity_radius.findAllByClassnameInRadius local ____CardSystem = require("cards.CardSystem") local ShowCardSelectionToPlayer = ____CardSystem.ShowCardSelectionToPlayer local ____crystal_currency = require("crystal_currency") local CrystalCurrency = ____crystal_currency.CrystalCurrency local ____black_shop_teleport = require("black_shop_teleport") local isBlackShopTeleportDestination = ____black_shop_teleport.isBlackShopTeleportDestination local resolveBlackShopTeleportPosition = ____black_shop_teleport.resolveBlackShopTeleportPosition local BLACKSHOP_GOLD_TO_CRYSTAL_RATE = 100 local BLACKSHOP_CRYSTAL_TO_GOLD_RATE = 25 local ItemQuality = ItemQuality or ({}) ItemQuality.COMMON = 0 ItemQuality[ItemQuality.COMMON] = "COMMON" ItemQuality.RARE = 1 ItemQuality[ItemQuality.RARE] = "RARE" ItemQuality.EPIC = 2 ItemQuality[ItemQuality.EPIC] = "EPIC" ItemQuality.LEGENDARY = 3 ItemQuality[ItemQuality.LEGENDARY] = "LEGENDARY" ItemQuality.CURSED = 4 ItemQuality[ItemQuality.CURSED] = "CURSED" ItemQuality.HEAVENLY = 5 ItemQuality[ItemQuality.HEAVENLY] = "HEAVENLY" ____exports.BlackShop = __TS__Class() local BlackShop = ____exports.BlackShop BlackShop.name = "BlackShop" BlackShop.____file_path = "scripts/vscripts/blackshop.lua" function BlackShop.prototype.____constructor(self) self.shopItems = {} self.spawnPoints = {} self.purchasedUniqueItems = __TS__New(Set) self.isUniqueItemSpawned = false self.cardPurchaseBaseCost = 5 self.cardPurchaseCostByPlayer = {} self:initializeItems() self:findSpawnPoints() self:SpawnItems() end function BlackShop.getInstance(self) if not ____exports.BlackShop.instance then ____exports.BlackShop.instance = __TS__New(____exports.BlackShop) end return ____exports.BlackShop.instance end function BlackShop.prototype.initializeItems(self) self.shopItems = { {name = "item_blackshop_common_bonus_stats_str", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_bonus_stats_int", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_bonus_stats_agi", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_injector", cost = 3, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_king_crown", cost = 10, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_manaflare", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_spell_mask", cost = 7, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_stone_armor", cost = 10, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_boo_stuff", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_vigor_tincture", cost = 4, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_wind_dust", cost = 4, quality = ItemQuality.COMMON}, {name = "item_blackshop_common_blue_tallow", cost = 5, quality = ItemQuality.COMMON}, {name = "item_blackshop_rare_damage_dagger", cost = 7, quality = ItemQuality.RARE}, {name = "item_blackshop_rare_agility_cape", cost = 14, quality = ItemQuality.RARE}, {name = "item_blackshop_rare_egg_of_death", cost = 10, quality = ItemQuality.RARE}, {name = "item_blackshop_rare_granite_badge", cost = 12, quality = ItemQuality.RARE}, {name = "item_blackshop_rare_iron_resolve", cost = 13, quality = ItemQuality.RARE}, {name = "item_blackshop_rare_silver_eye", cost = 12, quality = ItemQuality.RARE, uniqueItem = true}, {name = "item_blackshop_rare_critical_havoc", cost = 11, quality = ItemQuality.RARE, uniqueItem = true}, {name = "item_blackshop_epic_power_of_grow", cost = 15, quality = ItemQuality.EPIC, uniqueItem = true}, {name = "item_blackshop_epic_critical_paladin_sword", cost = 8, quality = ItemQuality.EPIC, uniqueItem = true}, {name = "item_blackshop_epic_trinity_seal", cost = 20, quality = ItemQuality.EPIC, uniqueItem = true}, {name = "item_blackshop_epic_bulwark_plate", cost = 22, quality = ItemQuality.EPIC, uniqueItem = true}, {name = "item_blackshop_legendary_fire_summoner", cost = 18, quality = ItemQuality.LEGENDARY, uniqueItem = true}, {name = "item_blackshop_legendary_primordial_shard", cost = 32, quality = ItemQuality.LEGENDARY}, { name = "item_blackshop_legendary_restock", cost = 12, quality = ItemQuality.LEGENDARY, uniqueItem = true, nonRefundable = true }, { name = "item_blackshop_legendary_twilight_mirror", cost = 38, quality = ItemQuality.LEGENDARY, uniqueItem = true, nonRefundable = true }, { name = "item_blackshop_legendary_astral_anchor", cost = 36, quality = ItemQuality.LEGENDARY, uniqueItem = true, nonRefundable = true }, {name = "item_blackshop_legendary_fated_die", cost = 26, quality = ItemQuality.LEGENDARY}, { name = "item_blackshop_cursed_the_hand_of_gluttony", cost = 60, quality = ItemQuality.CURSED, uniqueItem = true, nonRefundable = true }, {name = "item_blackshop_cursed_martyrs_brand", cost = 52, quality = ItemQuality.CURSED}, {name = "item_blackshop_cursed_widow_chain", cost = 50, quality = ItemQuality.CURSED}, {name = "item_blackshop_cursed_glass_pact", cost = 46, quality = ItemQuality.CURSED}, { name = "item_blackshop_heavenly_reset_to_zero", cost = 15, quality = ItemQuality.HEAVENLY, uniqueItem = true, nonRefundable = true }, { name = "item_blackshop_heavenly_font_of_mercy", cost = 48, quality = ItemQuality.HEAVENLY, uniqueItem = true, nonRefundable = true }, {name = "item_blackshop_heavenly_sanctuary_veil", cost = 32, quality = ItemQuality.HEAVENLY, uniqueItem = true}, {name = "item_blackshop_heavenly_dawn_chorus", cost = 34, quality = ItemQuality.HEAVENLY, uniqueItem = true} } end function BlackShop.prototype.findSpawnPoints(self) self.spawnPoints = {} do local i = 1 while i <= 5 do local entity = Entities:FindByName( nil, "blackshop_item_point_" .. tostring(i) ) if entity and not entity:IsNull() then local position = entity:GetOrigin() local ____self_spawnPoints_0 = self.spawnPoints ____self_spawnPoints_0[#____self_spawnPoints_0 + 1] = position end i = i + 1 end end end function BlackShop.prototype.SpawnItems(self) self.isUniqueItemSpawned = false __TS__ArrayForEach( self.spawnPoints, function(____, point, index) local itemsNearby = findAllByClassnameInRadius("dota_item_drop", point, 100) __TS__ArrayForEach( itemsNearby, function(____, entity) if entity and not entity:IsNull() then local containerItem = entity local actualItem = containerItem:GetContainedItem() if actualItem ~= nil and actualItem ~= nil then local itemName = actualItem:GetAbilityName() local shopItem = __TS__ArrayFind( self.shopItems, function(____, i) return i.name == itemName end ) if shopItem and shopItem.uniqueItem then self.isUniqueItemSpawned = true end end end end ) if #itemsNearby == 0 then local availableItems = __TS__ArrayFilter( self.shopItems, function(____, item) if item.uniqueItem then local isAvailable = not self.purchasedUniqueItems:has(item.name) and not self.isUniqueItemSpawned return isAvailable end return not self.purchasedUniqueItems:has(item.name) end ) if #availableItems == 0 then return end local randomItem = availableItems[RandomInt(0, #availableItems - 1) + 1] if randomItem.uniqueItem then self.isUniqueItemSpawned = true end local item = CreateItem(randomItem.name, nil, nil) local drop = CreateItemOnPositionForLaunch(point, item) drop:SetAbsOrigin(point) drop:SetAngles(0, 0, 0) local particleName repeat local ____switch21 = randomItem.quality local ____cond21 = ____switch21 == ItemQuality.COMMON if ____cond21 then particleName = "particles/units/heroes/hero_ancient_apparition/ancient_apparition_freeze_stacks_smoke_b.vpcf" break end ____cond21 = ____cond21 or ____switch21 == ItemQuality.RARE if ____cond21 then particleName = "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_main.vpcf" break end ____cond21 = ____cond21 or ____switch21 == ItemQuality.EPIC if ____cond21 then particleName = "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf" break end ____cond21 = ____cond21 or ____switch21 == ItemQuality.LEGENDARY if ____cond21 then particleName = "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf" break end ____cond21 = ____cond21 or ____switch21 == ItemQuality.CURSED if ____cond21 then particleName = "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf" break end ____cond21 = ____cond21 or ____switch21 == ItemQuality.HEAVENLY if ____cond21 then particleName = "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf" break end do particleName = "particles/default_item.vpcf" end until true local particle = ParticleManager:CreateParticle(particleName, PATTACH_ABSORIGIN_FOLLOW, drop) ParticleManager:SetParticleControl(particle, 0, point) end end ) end function BlackShop.prototype.RefreshShop(self) __TS__ArrayForEach( self.spawnPoints, function(____, point, index) local itemsNearby = findAllByClassnameInRadius("dota_item_drop", point, 100) __TS__ArrayForEach( itemsNearby, function(____, item) if item and not item:IsNull() then local containerItem = item local actualItem = containerItem:GetContainedItem() if not actualItem or not actualItem._wasPurchased then UTIL_Remove(item) end end end ) end ) self:SpawnItems() end function BlackShop.prototype.OnItemPickup(self, item, player) local itemName = item:GetAbilityName() local shopItem = __TS__ArrayFind( self.shopItems, function(____, i) return i.name == itemName end ) if not shopItem then return false end if shopItem.uniqueItem then self.purchasedUniqueItems:add(shopItem.name) end item._wasPurchased = true return true end function BlackShop.prototype.getItemInfo(self, itemName) return __TS__ArrayFind( self.shopItems, function(____, item) return item.name == itemName end ) end function BlackShop.prototype.getItemCost(self, itemName) local item = self:getItemInfo(itemName) return item and item.cost or 0 end function BlackShop.prototype.OnItemPurchased(self, itemName) local shopItem = __TS__ArrayFind( self.shopItems, function(____, i) return i.name == itemName end ) if shopItem and shopItem.uniqueItem then self.purchasedUniqueItems:add(itemName) end end function BlackShop.prototype.ResetUniqueItems(self) local currentItems = __TS__ArrayFrom(self.purchasedUniqueItems) __TS__ArrayForEach( currentItems, function(____, itemName) local item = __TS__ArrayFind( self.shopItems, function(____, i) return i.name == itemName end ) if item and not item.nonRefundable then self.purchasedUniqueItems:delete(itemName) elseif item and item.nonRefundable then end end ) self.isUniqueItemSpawned = false self:RefreshShop() end function BlackShop.prototype.getCardPurchaseCost(self, playerId) local currentCost = self.cardPurchaseCostByPlayer[playerId] if currentCost ~= nil and currentCost > 0 then return currentCost end self.cardPurchaseCostByPlayer[playerId] = self.cardPurchaseBaseCost return self.cardPurchaseBaseCost end function BlackShop.prototype.buyCardForPlayer(self, playerId) local player = PlayerResource:GetPlayer(playerId) local hero = player and player:GetAssignedHero() local currentCost = self:getCardPurchaseCost(playerId) if not player or not hero then return {success = false, spentCost = currentCost, nextCost = currentCost, errorMessage = "black_shop_not_enough_crystals"} end local crystalCurrency = CrystalCurrency:getInstance() local currentCrystals = crystalCurrency:getCrystals(playerId) if currentCrystals < currentCost then return {success = false, spentCost = currentCost, nextCost = currentCost, errorMessage = "black_shop_not_enough_crystals"} end crystalCurrency:removeCrystals(playerId, currentCost) ShowCardSelectionToPlayer(nil, playerId, 3, "black_shop_buy_card") local nextCost = currentCost * 2 self.cardPurchaseCostByPlayer[playerId] = nextCost return {success = true, spentCost = currentCost, nextCost = nextCost} end function BlackShop.prototype.SpawnItemAtPosition(self, position) local availableItems = __TS__ArrayFilter( self.shopItems, function(____, item) if item.uniqueItem then return not self.purchasedUniqueItems:has(item.name) and not self.isUniqueItemSpawned end return not self.purchasedUniqueItems:has(item.name) end ) if #availableItems == 0 then return end local randomItem = availableItems[RandomInt(0, #availableItems - 1) + 1] if randomItem.uniqueItem then self.isUniqueItemSpawned = true end local item = CreateItem(randomItem.name, nil, nil) local drop = CreateItemOnPositionForLaunch(position, item) drop:SetAbsOrigin(position) drop:SetAngles(0, 0, 0) local particleName repeat local ____switch52 = randomItem.quality local ____cond52 = ____switch52 == ItemQuality.COMMON if ____cond52 then particleName = "particles/common_item.vpcf" break end ____cond52 = ____cond52 or ____switch52 == ItemQuality.RARE if ____cond52 then particleName = "particles/rare_item.vpcf" break end ____cond52 = ____cond52 or ____switch52 == ItemQuality.EPIC if ____cond52 then particleName = "particles/epic_item.vpcf" break end ____cond52 = ____cond52 or ____switch52 == ItemQuality.LEGENDARY if ____cond52 then particleName = "particles/legendary_item.vpcf" break end ____cond52 = ____cond52 or ____switch52 == ItemQuality.CURSED if ____cond52 then particleName = "particles/cursed_item.vpcf" break end ____cond52 = ____cond52 or ____switch52 == ItemQuality.HEAVENLY if ____cond52 then particleName = "particles/heavenly_item_effect.vpcf" break end do particleName = "particles/default_item.vpcf" end until true local particle = ParticleManager:CreateParticle(particleName, PATTACH_ABSORIGIN_FOLLOW, drop) ParticleManager:SetParticleControl(particle, 0, position) end --- Регистрация CGE для блекшопа — только из GameMode.Activate (или иного позднего входа). -- При require() из addon_init CustomGameEventManager ещё nil — нельзя вызывать в main chunk. function ____exports.registerBlackShopCustomGameEvents(self) if not IsServer() then return end CustomGameEventManager:RegisterListener( "black_shop_refresh_request", function(_, event) local playerId = event.PlayerID local ____opt_9 = PlayerResource:GetPlayer(playerId) local hero = ____opt_9 and ____opt_9:GetAssignedHero() local refreshCost = 50 if not hero then return end local currentGold = hero:GetGold() if currentGold >= refreshCost then PlayerResource:SpendGold(playerId, refreshCost, DOTA_ModifyGold_AbilityGold) ____exports.BlackShop:getInstance():RefreshShop() CustomGameEventManager:Send_ServerToAllClients("black_shop_refresh", {success = true}) else CustomGameEventManager:Send_ServerToPlayer( PlayerResource:GetPlayer(playerId), "CreateIngameErrorMessage", {gold = refreshCost - currentGold, message = "black_shop_not_enough_gold"} ) end end ) CustomGameEventManager:RegisterListener( "black_shop_request_card_price", function(_, event) local playerId = event.PlayerID local player = PlayerResource:GetPlayer(playerId) if not player then return end local cost = ____exports.BlackShop:getInstance():getCardPurchaseCost(playerId) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_card_cost_update", {cost = cost}) end ) CustomGameEventManager:RegisterListener( "black_shop_buy_card_request", function(_, event) local playerId = event.PlayerID local player = PlayerResource:GetPlayer(playerId) if not player then return end local result = ____exports.BlackShop:getInstance():buyCardForPlayer(playerId) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_card_cost_update", {cost = result.nextCost}) if not result.success then local crystalCurrency = CrystalCurrency:getInstance() local currentCrystals = crystalCurrency:getCrystals(playerId) CustomGameEventManager:Send_ServerToPlayer( player, "CreateIngameErrorMessage", { crystals = math.max(0, result.spentCost - currentCrystals), message = result.errorMessage or "black_shop_not_enough_crystals" } ) return end CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_card_purchased", {success = true}) end ) CustomGameEventManager:RegisterListener( "black_shop_teleport_request", function(_, event) local playerId = event.PlayerID if playerId == nil then return end local player = PlayerResource:GetPlayer(playerId) if not player then return end local dest = event.destination if not isBlackShopTeleportDestination(nil, dest) then return end local hero = player:GetAssignedHero() if not hero or hero:IsNull() then return end if not hero:IsAlive() then CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_teleport_result", {success = false}) return end local teleportGoldCost = 100 local currentGold = hero:GetGold() if currentGold < teleportGoldCost then CustomGameEventManager:Send_ServerToPlayer(player, "CreateIngameErrorMessage", {gold = teleportGoldCost - currentGold, message = "black_shop_not_enough_gold"}) return end local pos = resolveBlackShopTeleportPosition(nil, dest) if not pos then return end PlayerResource:SpendGold(playerId, teleportGoldCost, DOTA_ModifyGold_AbilityGold) FindClearSpaceForUnit(hero, pos, true) local teleportFx = "particles/econ/events/compendium_2023/compendium_2023_teleport_lvl1.vpcf" local origin = hero:GetAbsOrigin() local pfx = ParticleManager:CreateParticle(teleportFx, PATTACH_WORLDORIGIN, hero) ParticleManager:SetParticleControl(pfx, 0, origin) Timers:CreateTimer( 0.5, function() ParticleManager:DestroyParticle(pfx, true) ParticleManager:ReleaseParticleIndex(pfx) end ) EmitSoundOn("DOTA_Item.BlinkDagger.Activate", hero) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_teleport_result", {success = true}) end ) CustomGameEventManager:RegisterListener( "black_shop_exchange_request", function(_, event) local playerId = event.PlayerID or event.player_id if playerId == nil then return end local player = PlayerResource:GetPlayer(playerId) local hero = player and player:GetAssignedHero() if not player or not hero then return end local direction = event.direction local crystals = math.floor(tonumber(event.crystals) or 0) if not direction or crystals <= 0 then return end local crystalCurrency = CrystalCurrency:getInstance() if direction == "gold_to_crystal" then local goldAmount = crystals * BLACKSHOP_GOLD_TO_CRYSTAL_RATE local currentGold = hero:GetGold() if currentGold < goldAmount then CustomGameEventManager:Send_ServerToPlayer(player, "CreateIngameErrorMessage", {gold = goldAmount - currentGold, message = "black_shop_not_enough_gold"}) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_exchange_result", {success = false}) return end PlayerResource:SpendGold(playerId, goldAmount, DOTA_ModifyGold_AbilityGold) crystalCurrency:addCrystals(playerId, crystals) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_exchange_result", {success = true, direction = direction, crystals = crystals, gold = goldAmount}) else local goldAmount = crystals * BLACKSHOP_CRYSTAL_TO_GOLD_RATE local currentCrystals = crystalCurrency:getCrystals(playerId) if currentCrystals < crystals then CustomGameEventManager:Send_ServerToPlayer(player, "CreateIngameErrorMessage", {crystals = crystals - currentCrystals, message = "black_shop_not_enough_crystals"}) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_exchange_result", {success = false}) return end local removed = crystalCurrency:removeCrystals(playerId, crystals) if not removed then CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_exchange_result", {success = false}) return end hero:ModifyGold(goldAmount, true, DOTA_ModifyGold_AbilityGold) CustomGameEventManager:Send_ServerToPlayer(player, "black_shop_exchange_result", {success = true, direction = direction, crystals = crystals, gold = goldAmount}) end end ) CustomGameEventManager:RegisterListener( "black_shop_exchange_sync_request", function(_, event) local playerId = event.PlayerID or event.player_id if playerId == nil then return end local player = PlayerResource:GetPlayer(playerId) local hero = player and player:GetAssignedHero() if not player or not hero then return end local crystalCurrency = CrystalCurrency:getInstance() CustomGameEventManager:Send_ServerToPlayer( player, "black_shop_exchange_sync", { gold = hero:GetGold(), crystals = crystalCurrency:getCrystals(playerId) } ) end ) end --- Превью дропа / эффекты редкости предметов function ____exports.precacheBlackshopParticles(self, context) PrecacheResource("particle", "particles/econ/events/compendium_2023/compendium_2023_teleport_lvl1.vpcf", context) PrecacheResource("particle", "particles/units/heroes/hero_ancient_apparition/ancient_apparition_freeze_stacks_smoke_b.vpcf", context) PrecacheResource("particle", "particles/units/heroes/hero_ancient_apparition/ancient_apparition_ice_blast_main.vpcf", context) PrecacheResource("particle", "particles/econ/items/lanaya/lanaya_epit_trap/templar_assassin_epit_trap.vpcf", context) PrecacheResource("particle", "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_gold.vpcf", context) PrecacheResource("particle", "particles/econ/items/shadow_fiend/sf_desolation/sf_base_attack_desolation.vpcf", context) PrecacheResource("particle", "particles/econ/items/templar_assassin/ta_2022_immortal/ta_2022_immortal_trap_crimson.vpcf", context) end return ____exports