385 lines
13 KiB
Lua
385 lines
13 KiB
Lua
local ____lualib = require("lualib_bundle")
|
|
local __TS__Class = ____lualib.__TS__Class
|
|
local __TS__New = ____lualib.__TS__New
|
|
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
|
|
local ____exports = {}
|
|
local ____difficulty_manager = require("difficulty_manager")
|
|
local Difficulty = ____difficulty_manager.Difficulty
|
|
local ____contracts_registry = require("death_sentence.contracts_registry")
|
|
local getDeathSentenceItemDropChanceMultiplier = ____contracts_registry.getDeathSentenceItemDropChanceMultiplier
|
|
local ____luck = require("utils.luck")
|
|
local getLuck = ____luck.getLuck
|
|
local rollLuckChance = ____luck.rollLuckChance
|
|
local ____get_true_hero_from_entity = require("utils.get_true_hero_from_entity")
|
|
local getTrueHeroFromEntity = ____get_true_hero_from_entity.getTrueHeroFromEntity
|
|
--- Маркер на CDOTA_Item: дроп из этой системы (KV без стакаемой шаряемости тоже обрабатываем по флагу).
|
|
local ITEM_DROP_SYSTEM_MARK = "_item_drop_system_world_loot"
|
|
local KV_FULLY_SHAREABLE_STACKING = "ITEM_FULLY_SHAREABLE_STACKING"
|
|
--- Скан слотов инвентаря героя (основной + рюкзак + нейтральные слоты при наличии).
|
|
local HERO_ITEM_SLOT_SCAN_MAX = 16
|
|
____exports.ItemDropSystem = __TS__Class()
|
|
local ItemDropSystem = ____exports.ItemDropSystem
|
|
ItemDropSystem.name = "ItemDropSystem"
|
|
ItemDropSystem.____file_path = "scripts/vscripts/item_drops.lua"
|
|
function ItemDropSystem.prototype.____constructor(self)
|
|
ListenToGameEvent(
|
|
"entity_killed",
|
|
function(event) return ____exports.ItemDropSystem:OnEntityKilled(event) end,
|
|
nil
|
|
)
|
|
ListenToGameEvent(
|
|
"dota_item_picked_up",
|
|
function(event) return ____exports.ItemDropSystem:OnItemPickedUp(event) end,
|
|
nil
|
|
)
|
|
end
|
|
function ItemDropSystem.Initialize(self)
|
|
if not ____exports.ItemDropSystem.instance then
|
|
____exports.ItemDropSystem.instance = __TS__New(____exports.ItemDropSystem)
|
|
end
|
|
end
|
|
function ItemDropSystem.spawnLootOnGround(self, position, itemName, quantity, despawnTime)
|
|
if quantity <= 0 then
|
|
return
|
|
end
|
|
local items = {}
|
|
local first = CreateItem(itemName, nil, nil)
|
|
if not first then
|
|
return
|
|
end
|
|
if quantity > 1 and first:GetInitialCharges() == 1 then
|
|
first:SetCurrentCharges(quantity)
|
|
items[#items + 1] = first
|
|
else
|
|
items[#items + 1] = first
|
|
do
|
|
local i = 1
|
|
while i < quantity do
|
|
local extra = CreateItem(itemName, nil, nil)
|
|
if extra then
|
|
items[#items + 1] = extra
|
|
end
|
|
i = i + 1
|
|
end
|
|
end
|
|
end
|
|
for ____, item in ipairs(items) do
|
|
item[ITEM_DROP_SYSTEM_MARK] = true
|
|
local physicalItem = CreateItemOnPositionForLaunch(position, item)
|
|
local dropRadius = RandomFloat(50, 100)
|
|
item:LaunchLootInitialHeight(
|
|
false,
|
|
0,
|
|
150,
|
|
0.5,
|
|
position + RandomVector(dropRadius)
|
|
)
|
|
if despawnTime and despawnTime > 0 then
|
|
Timers:CreateTimer(
|
|
despawnTime,
|
|
function()
|
|
if not physicalItem or not IsValidEntity(physicalItem) then
|
|
return
|
|
end
|
|
local owner = physicalItem:GetOwner()
|
|
if not owner then
|
|
UTIL_Remove(physicalItem)
|
|
end
|
|
end
|
|
)
|
|
end
|
|
end
|
|
end
|
|
function ItemDropSystem.SpawnRandomDropsOnDeath(self, unit, killingHero)
|
|
if not unit then
|
|
return
|
|
end
|
|
local position = unit:GetAbsOrigin()
|
|
local unitName = unit:GetUnitName()
|
|
local dsDropMult = getDeathSentenceItemDropChanceMultiplier(
|
|
nil,
|
|
Difficulty:getWinningContractSnapshot()
|
|
)
|
|
local luckHero = killingHero ~= nil and killingHero ~= nil and IsValidEntity(killingHero) and killingHero:IsRealHero() and not killingHero:IsIllusion() and killingHero or nil
|
|
for ____, dropItem in ipairs(self.possibleDrops) do
|
|
do
|
|
if #dropItem.unitTypes > 0 and not __TS__ArrayIncludes(dropItem.unitTypes, unitName) then
|
|
goto __continue22
|
|
end
|
|
local effectiveChance = dropItem.chance >= 100 and 100 or math.max(
|
|
0,
|
|
math.floor(dropItem.chance * dsDropMult + 1e-9)
|
|
)
|
|
if effectiveChance <= 0 then
|
|
goto __continue22
|
|
end
|
|
local baseFraction = effectiveChance / 100
|
|
local ____temp_0
|
|
if luckHero ~= nil then
|
|
____temp_0 = rollLuckChance(nil, luckHero, baseFraction)
|
|
else
|
|
____temp_0 = RandomInt(1, 100) <= effectiveChance
|
|
end
|
|
local dropped = ____temp_0
|
|
if dropped then
|
|
local quantity
|
|
if type(dropItem.quantity) == "number" then
|
|
quantity = dropItem.quantity
|
|
else
|
|
local q = dropItem.quantity
|
|
quantity = RandomInt(q.min, q.max)
|
|
if luckHero ~= nil and q.max > q.min then
|
|
local luckBonus = math.max(
|
|
0,
|
|
math.floor(getLuck(nil, luckHero) / 10)
|
|
)
|
|
quantity = quantity + luckBonus
|
|
end
|
|
end
|
|
____exports.ItemDropSystem:spawnLootOnGround(position, dropItem.itemName, quantity, dropItem.despawnTime)
|
|
end
|
|
end
|
|
::__continue22::
|
|
end
|
|
end
|
|
function ItemDropSystem.readItemShareabilityKv(self, item)
|
|
do
|
|
local ____try, ____hasReturned, ____returnValue = pcall(function()
|
|
local kv = item:GetAbilityKeyValues()
|
|
if kv and type(kv) == "table" then
|
|
local v = kv.ItemShareability
|
|
if v ~= nil and v ~= nil then
|
|
return true, tostring(v)
|
|
end
|
|
end
|
|
end)
|
|
if ____try and ____hasReturned then
|
|
return ____returnValue
|
|
end
|
|
end
|
|
do
|
|
local ____try, ____hasReturned, ____returnValue = pcall(function()
|
|
local anyItem = item
|
|
local ____opt_1 = anyItem.GetAbilityKeyValue
|
|
local v2 = ____opt_1 and ____opt_1(anyItem, "ItemShareability")
|
|
if v2 ~= nil and v2 ~= nil then
|
|
return true, tostring(v2)
|
|
end
|
|
end)
|
|
if ____try and ____hasReturned then
|
|
return ____returnValue
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
function ItemDropSystem.isFullyShareableStackingKv(self, item)
|
|
return ____exports.ItemDropSystem:readItemShareabilityKv(item) == KV_FULLY_SHAREABLE_STACKING
|
|
end
|
|
function ItemDropSystem.isKnownWorldDropItemName(self, itemName)
|
|
for ____, row in ipairs(____exports.ItemDropSystem.possibleDrops) do
|
|
if row.itemName == itemName then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
function ItemDropSystem.slotQualifiesForPurchaserRefresh(self, it, itemName, includeDropTableName)
|
|
if it:GetAbilityName() ~= itemName then
|
|
return false
|
|
end
|
|
local mark = it
|
|
if mark[ITEM_DROP_SYSTEM_MARK] == true then
|
|
return true
|
|
end
|
|
if ____exports.ItemDropSystem:isFullyShareableStackingKv(it) then
|
|
return true
|
|
end
|
|
return includeDropTableName and ____exports.ItemDropSystem:isKnownWorldDropItemName(itemName)
|
|
end
|
|
function ItemDropSystem.applyPurchaserToHeroSlotsForItem(self, hero, itemName, includeDropTableName)
|
|
if not hero or not hero:IsRealHero() or not itemName then
|
|
return
|
|
end
|
|
do
|
|
local slot = 0
|
|
while slot < HERO_ITEM_SLOT_SCAN_MAX do
|
|
do
|
|
local it = hero:GetItemInSlot(slot)
|
|
if not it or it:IsNull() then
|
|
goto __continue47
|
|
end
|
|
if not ____exports.ItemDropSystem:slotQualifiesForPurchaserRefresh(it, itemName, includeDropTableName) then
|
|
goto __continue47
|
|
end
|
|
do
|
|
pcall(function()
|
|
local ____this_4
|
|
____this_4 = it
|
|
local ____opt_3 = ____this_4.SetPurchaser
|
|
if ____opt_3 ~= nil then
|
|
____opt_3(____this_4, hero)
|
|
end
|
|
end)
|
|
end
|
|
end
|
|
::__continue47::
|
|
slot = slot + 1
|
|
end
|
|
end
|
|
end
|
|
function ItemDropSystem.schedulePurchaserRefreshAfterPickup(self, hero, itemName, includeDropTableName)
|
|
local function run()
|
|
return ____exports.ItemDropSystem:applyPurchaserToHeroSlotsForItem(hero, itemName, includeDropTableName)
|
|
end
|
|
run(nil)
|
|
Timers:CreateTimer(
|
|
0,
|
|
function()
|
|
run(nil)
|
|
return nil
|
|
end
|
|
)
|
|
Timers:CreateTimer(
|
|
0.06,
|
|
function()
|
|
run(nil)
|
|
return nil
|
|
end
|
|
)
|
|
Timers:CreateTimer(
|
|
0.15,
|
|
function()
|
|
run(nil)
|
|
return nil
|
|
end
|
|
)
|
|
end
|
|
function ItemDropSystem.OnItemPickedUp(self, event)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local heroIndex = event.HeroEntityIndex
|
|
local itemIndex = event.ItemEntityIndex
|
|
if heroIndex == nil or heroIndex == nil or itemIndex == nil or itemIndex == nil then
|
|
return
|
|
end
|
|
local hero = EntIndexToHScript(heroIndex)
|
|
local item = EntIndexToHScript(itemIndex)
|
|
if not hero or not hero:IsRealHero() or not item or not IsValidEntity(item) then
|
|
return
|
|
end
|
|
local itemName = item:GetAbilityName()
|
|
if not itemName then
|
|
return
|
|
end
|
|
local mark = item
|
|
local fromWorldDrop = mark[ITEM_DROP_SYSTEM_MARK] == true
|
|
local stackingShareable = ____exports.ItemDropSystem:isFullyShareableStackingKv(item)
|
|
if not fromWorldDrop and not stackingShareable then
|
|
return
|
|
end
|
|
____exports.ItemDropSystem:schedulePurchaserRefreshAfterPickup(hero, itemName, fromWorldDrop)
|
|
end
|
|
function ItemDropSystem.OnEntityKilled(self, event)
|
|
if not IsServer() then
|
|
return
|
|
end
|
|
local killedUnit = EntIndexToHScript(event.entindex_killed)
|
|
if not killedUnit or not killedUnit:IsBaseNPC() then
|
|
return
|
|
end
|
|
local attackerIndex = event.entindex_attacker
|
|
local ____temp_5
|
|
if attackerIndex ~= nil and attackerIndex ~= nil and attackerIndex > 0 then
|
|
____temp_5 = EntIndexToHScript(attackerIndex)
|
|
else
|
|
____temp_5 = nil
|
|
end
|
|
local attackerEnt = ____temp_5
|
|
local ____attackerEnt_6
|
|
if attackerEnt then
|
|
____attackerEnt_6 = getTrueHeroFromEntity(nil, attackerEnt)
|
|
else
|
|
____attackerEnt_6 = nil
|
|
end
|
|
local killingHero = ____attackerEnt_6
|
|
____exports.ItemDropSystem:SpawnRandomDropsOnDeath(killedUnit, killingHero)
|
|
end
|
|
function ItemDropSystem.getInstance(self)
|
|
if not ____exports.ItemDropSystem.instance then
|
|
____exports.ItemDropSystem.instance = __TS__New(____exports.ItemDropSystem)
|
|
end
|
|
return ____exports.ItemDropSystem.instance
|
|
end
|
|
ItemDropSystem.possibleDrops = {
|
|
{
|
|
itemName = "item_meat",
|
|
chance = 50,
|
|
quantity = {min = 1, max = 3},
|
|
unitTypes = {"npc_pig"},
|
|
despawnTime = 15
|
|
},
|
|
{
|
|
itemName = "item_milk",
|
|
chance = 50,
|
|
quantity = {min = 1, max = 3},
|
|
unitTypes = {"npc_sheep"},
|
|
despawnTime = 15
|
|
},
|
|
{
|
|
itemName = "item_wolf_claw",
|
|
chance = 35,
|
|
quantity = {min = 1, max = 2},
|
|
unitTypes = {"npc_wolf"},
|
|
despawnTime = 15
|
|
},
|
|
{
|
|
itemName = "item_ent_heart",
|
|
chance = 80,
|
|
quantity = {min = 1, max = 2},
|
|
unitTypes = {"npc_ent"},
|
|
despawnTime = 15
|
|
},
|
|
{
|
|
itemName = "item_frog_paw",
|
|
chance = 20,
|
|
quantity = {min = 1, max = 4},
|
|
unitTypes = {"npc_frogman_magi"},
|
|
despawnTime = 25
|
|
},
|
|
{
|
|
itemName = "item_frog_paw",
|
|
chance = 15,
|
|
quantity = {min = 1, max = 4},
|
|
unitTypes = {"npc_frop_tadpole"},
|
|
despawnTime = 25
|
|
},
|
|
{
|
|
itemName = "item_frog_paw",
|
|
chance = 10,
|
|
quantity = {min = 1, max = 4},
|
|
unitTypes = {"npc_small_frog_froglet"},
|
|
despawnTime = 25
|
|
},
|
|
{
|
|
itemName = "item_frog_paw",
|
|
chance = 5,
|
|
quantity = {min = 1, max = 2},
|
|
unitTypes = {"npc_mini_frog"},
|
|
despawnTime = 25
|
|
},
|
|
{itemName = "item_poison", chance = 75, quantity = 1, unitTypes = {"npc_venomancer_brute", "npc_ravenous_woodfang", "npc_lycosidae_stalker"}},
|
|
{itemName = "item_spider_legs_custom", chance = 40, quantity = {min = 1, max = 6}, unitTypes = {"npc_ravenous_woodfang", "npc_lycosidae_stalker"}},
|
|
{itemName = "item_aghanims_shard_roshan", chance = 100, quantity = 1, unitTypes = {"npc_witch"}},
|
|
{itemName = "item_lycan_horn", chance = 100, quantity = 1, unitTypes = {"npc_boss_lycan"}},
|
|
{itemName = "item_wooden_katana", chance = 100, quantity = 1, unitTypes = {"npc_sakura_tree"}},
|
|
{
|
|
itemName = "item_egg",
|
|
chance = 20,
|
|
quantity = 1,
|
|
unitTypes = {"npc_chicken"},
|
|
despawnTime = 25
|
|
}
|
|
}
|
|
return ____exports
|